Merge pull request #122 from lbryio/client-side-input
Client side input
This commit is contained in:
commit
8f269cf193
7 changed files with 167 additions and 109 deletions
|
@ -123,6 +123,7 @@ table {
|
|||
.input-error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
|
|
|
@ -61,6 +61,12 @@ function showError(elementId, 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
|
||||
function FileError(message) {
|
||||
this.name = 'FileError';
|
||||
|
|
|
@ -2,96 +2,6 @@
|
|||
function updatePublishStatus(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 */
|
||||
|
||||
|
@ -115,13 +25,14 @@ function previewAndStageFile(selectedFile){
|
|||
previewReader.onloadend = function () {
|
||||
dropzone.style.display = 'none';
|
||||
previewHolder.style.display = 'block';
|
||||
previewHolder.innerHTML = '<img width="100%" src="' + previewReader.result + '" alt="image preview"/>';
|
||||
previewHolder.innerHTML = '<img width="100%" src="' + previewReader.result + '" alt="image preview"/>';
|
||||
};
|
||||
}
|
||||
// set the name input value to the image name if none is set yet
|
||||
if (nameInput.value === "") {
|
||||
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
|
||||
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="/siofu/client.js"></script>
|
||||
<script src="/assets/js/generalFunctions.js"></script>
|
||||
<script src="/assets/js/validationFunctions.js"></script>
|
||||
<script src="/assets/js/publishFunctions.js"></script>
|
||||
<script typ="text/javascript">
|
||||
// define variables
|
||||
|
@ -58,10 +59,5 @@
|
|||
document.getElementById('publish-active-area').innerHTML = publishResults;
|
||||
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>
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
<div class="panel">
|
||||
<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)">
|
||||
<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>
|
||||
<div id="asset-preview-holder"></div>
|
||||
</div>
|
||||
<div class="col-right">
|
||||
<textarea id="direct-link-holder" hidden="true">No URL yet</textarea>
|
||||
<div id="publish-active-area">
|
||||
<div class="input-error" id="input-error-claim-name" hidden="true"></div>
|
||||
<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>
|
||||
<p class="stop-float">
|
||||
<label for="publish-license">License:</label>
|
||||
|
@ -30,9 +28,33 @@
|
|||
<p>
|
||||
<div class="input-error" id="input-error-publish-submit" hidden="true"></div>
|
||||
<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><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>
|
||||
|
||||
<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}}
|
||||
<a href="/{{this.name}}/{{this.claimId}}">
|
||||
{{#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}}" >
|
||||
{{!--fallback--}}
|
||||
Your browser does not support the <code>video</code> element.
|
||||
</video>
|
||||
{{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}}
|
||||
</a>
|
||||
{{/unless}}
|
||||
|
@ -29,8 +29,7 @@
|
|||
itemSelector: '.grid-item'
|
||||
});
|
||||
|
||||
function resetLayout() {
|
||||
console.log('resetting layout');
|
||||
function resetTrendingLayout() {
|
||||
msnry.layout();
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue