This repository has been archived on 2024-06-26. You can view files and clone it, but cannot push or open issues or pull requests.
sakura/content/data/yuuno/js/yuuno.js

845 lines
22 KiB
JavaScript
Raw Normal View History

/*
* Sakura Yuuno JavaScript
*/
2015-05-05 03:47:58 +00:00
// Get or set cookies
function cookieData(action, name, data) {
2015-05-05 03:47:58 +00:00
switch(action) {
2015-05-05 03:47:58 +00:00
case 'get':
return (result = new RegExp('(^|; )' + encodeURIComponent(name) + '=([^;]*)').exec(document.cookie)) ? result[2] : '';
2015-05-05 03:47:58 +00:00
case 'set':
document.cookie = name + '=' + data;
2015-05-25 18:18:56 +00:00
return null;
2015-05-05 03:47:58 +00:00
default:
2015-05-25 18:18:56 +00:00
return null;
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
// Toggling the menu on mobile devices
function mobileMenu(mode) {
2015-05-05 03:47:58 +00:00
// Assign the elements to variables
var ucpMenuBtn = document.getElementById('navMenuSite');
var navMenuBtn = document.getElementById('navMenuUser');
var mobMenuBtn = document.getElementById('mobileNavToggle');
2015-05-05 03:47:58 +00:00
// Open or close the menus depending on the values
if(mode) {
2015-05-05 03:47:58 +00:00
// Alter the classes
ucpMenuBtn.className = ucpMenuBtn.className + ' menu-hid';
navMenuBtn.className = navMenuBtn.className + ' menu-hid';
2015-05-05 03:47:58 +00:00
// Update the button
mobMenuBtn.innerHTML = 'Close Menu';
mobMenuBtn.setAttribute('onclick', 'mobileMenu(false);');
2015-05-05 03:47:58 +00:00
} else {
2015-05-05 03:47:58 +00:00
// Alter the classes
ucpMenuBtn.className = ucpMenuBtn.className.replace(' menu-hid', '');
navMenuBtn.className = navMenuBtn.className.replace(' menu-hid', '');
2015-05-05 03:47:58 +00:00
// Update the button
mobMenuBtn.innerHTML = 'Open Menu';
mobMenuBtn.setAttribute('onclick', 'mobileMenu(true);');
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
// Get the current unix/epoch timestamp
function epochTime() {
2015-05-05 03:47:58 +00:00
return Math.floor(Date.now() / 1000);
}
2015-05-09 00:56:55 +00:00
// Create a notification box
function notifyUI(content) {
// Grab the container and create an ID
var container = document.getElementById('notifications');
var identifier = 'sakura-notification-' + Date.now();
// Create the notification element and children
var notif = document.createElement('div');
var notifIcon = document.createElement('div');
var notifContent = document.createElement('div');
var notifTitle = document.createElement('div');
var notifText = document.createElement('div');
var notifClose = document.createElement('div');
var notifClear = document.createElement('div');
2015-05-25 18:18:56 +00:00
var iconCont;
2015-05-09 00:56:55 +00:00
// Add ID and class on notification container
notif.className = 'notification-enter';
notif.setAttribute('id', identifier);
// Add icon
notifIcon .className = 'notification-icon';
if(content.img.substring(0, 5) == "FONT:") {
2015-05-25 18:18:56 +00:00
iconCont = document.createElement('div');
2015-05-09 00:56:55 +00:00
iconCont.className = 'font-icon fa ' + content.img.replace('FONT:', '') + ' fa-4x';
} else {
2015-05-25 18:18:56 +00:00
iconCont = document.createElement('img');
2015-05-09 00:56:55 +00:00
iconCont.setAttribute('alt', identifier);
iconCont.setAttribute('src', content.img);
}
2015-05-09 00:56:55 +00:00
notifIcon .appendChild(iconCont);
notif .appendChild(notifIcon);
// Add content
var notifTitleNode = document.createTextNode(content.title);
var notifTextNode = document.createTextNode(content.text);
notifContent .className = 'notification-content';
notifTitle .className = 'notification-title';
notifText .className = 'notification-text';
notifTitle .appendChild(notifTitleNode);
notifText .appendChild(notifTextNode);
if(content.link) {
notif .setAttribute('sakurahref', content.link);
notifContent.setAttribute('onclick', 'notifyOpen(this.parentNode.id);');
}
notifContent .appendChild(notifTitle);
notifContent .appendChild(notifText);
notif .appendChild(notifContent);
// Add close button
notifClose .className = 'notification-close';
notifClose .setAttribute('onclick', 'notifyClose(this.parentNode.id);');
notif .appendChild(notifClose);
// Add .clear
notifClear .className = 'clear';
notif .appendChild(notifClear);
// Append the notification to the document so it actually shows up to the user also add the link
container.appendChild(notif);
// Play sound if requested
if(content.sound > 0) {
// Create sound element and mp3 and ogg sources
var sound = document.createElement('audio');
var soundMP3 = document.createElement('source');
var soundOGG = document.createElement('source');
// Assign the proper attributes to the sources
soundMP3.setAttribute('src', '//' + sakuraVars.urls.content + '/sounds/notify.mp3');
soundMP3.setAttribute('type', 'audio/mp3');
soundOGG.setAttribute('src', '//' + sakuraVars.urls.content + '/sounds/notify.ogg');
soundOGG.setAttribute('type', 'audio/ogg');
// Append the children
sound.appendChild(soundMP3);
sound.appendChild(soundOGG);
// Play the sound
sound.play();
}
2015-05-09 00:56:55 +00:00
// If keepalive is 0 keep the notification open "forever" (until the user closes it or changes the page)
if(content.timeout > 0) {
// Set set a timeout and execute notifyClose() after amount of milliseconds specified
setTimeout(function() {
// Use the later defined notifyClose function
notifyClose(identifier);
}, content.timeout);
}
2015-05-09 00:56:55 +00:00
}
// Closing a notification box
function notifyClose(id) {
// Get the element and assign it to a variable
var element = document.getElementById(id);
// Do the animation
element.className = 'notification-exit';
// Remove the element after 500 milliseconds (animation takes 400)
setTimeout(function() {
// Use the later defined removeId function
removeId(id);
}, 410);
}
// Opening a link to a notifcated thing (what even)
function notifyOpen(id) {
var sakuraHref = document.getElementById(id).getAttribute('sakurahref');
2015-05-25 18:18:56 +00:00
if(typeof sakuraHref !== 'undefined') {
2015-05-09 00:56:55 +00:00
window.location = sakuraHref;
2015-05-25 18:18:56 +00:00
}
2015-05-09 00:56:55 +00:00
}
// Request notifications
function notifyRequest(session) {
// Create XMLHttpRequest and notifyURL
var notificationWatcher = new XMLHttpRequest();
var notifyURL = '//' + sakuraVars.urls.main + '/settings.php?request-notifications=true&time=' + epochTime() + '&session=' + session;
// Wait for the ready state to change
notificationWatcher.onreadystatechange = function() {
// Wait for it to reach the "complete" stage
if(notificationWatcher.readyState === 4) {
// Continue if the HTTP return was 200
if(notificationWatcher.status === 200) {
// Assign the JSON parsed content to a variable
var notifyGet = JSON.parse(notificationWatcher.responseText);
// If nothing was set stop
if(typeof notifyGet == 'undefined') {
// Tell the user something went wrong...
notifyUI({
"title": "An error occurred!",
"text": "If this problem persists please report this to the administrator.",
"img": "FONT:fa-exclamation-triangle",
"timeout": 60000,
"sound": false
});
// ...and log an error message to to console..
console.log('[SAKURA NOTIFICATION DEBUG] Invalid return type.');
// ...then prevent the function from contiuing
return;
}
// Go over every return notification and pass the object to it
2015-05-25 18:18:56 +00:00
for(var notifyID in notifyGet) {
2015-05-09 00:56:55 +00:00
notifyUI(notifyGet[notifyID]);
2015-05-25 18:18:56 +00:00
}
} else {
2015-05-09 00:56:55 +00:00
// ELse tell the user there was an internal server error...
notifyUI({
"title": "An internal server error occurred!",
"text": "If this problem persists please report this to the administrator.",
"img": "FONT:fa-chain-broken",
"timeout": 60000,
"sound": false
});
// ...and log a thing to the JavaScript console
console.log('[SAKURA NOTIFICATION DEBUG] HTTP return wasn\'t 200.');
}
2015-05-09 00:56:55 +00:00
}
2015-05-09 00:56:55 +00:00
2015-05-25 18:18:56 +00:00
};
2015-05-09 00:56:55 +00:00
// Make the request
notificationWatcher.open('GET', notifyURL, true);
notificationWatcher.send();
}
2015-05-05 03:47:58 +00:00
// Removing all elements with a certain class
function removeClass(className) {
2015-05-05 03:47:58 +00:00
// Get the elements
var objectCont = document.getElementsByClassName(className);
2015-05-05 03:47:58 +00:00
// Use a while loop instead of a for loop (Array keys change) to remove each element
2015-05-25 18:18:56 +00:00
while(objectCont.length > 0) {
objectCont[0].parentNode.removeChild(objectCont[0]);
2015-05-05 03:47:58 +00:00
2015-05-25 18:18:56 +00:00
}
}
2015-05-05 03:47:58 +00:00
// Removing an element by ID
function removeId(id) {
2015-05-05 03:47:58 +00:00
// Get the element
var objectCont = document.getElementById(id);
2015-05-05 03:47:58 +00:00
// If the element exists use the parent node to remove it
2015-05-25 18:18:56 +00:00
if(typeof(objectCont) != "undefined" && objectCont !== null) {
objectCont.parentNode.removeChild(objectCont);
2015-05-05 03:47:58 +00:00
2015-05-25 18:18:56 +00:00
}
}
2015-05-05 03:47:58 +00:00
// Show the full-page busy window
function ajaxBusyView(show, message, type) {
2015-05-05 03:47:58 +00:00
// Get elements
var busyCont = document.getElementById('ajaxBusy');
var busyStat = document.getElementById('ajaxStatus');
var busyAnim = document.getElementById('ajaxAnimate');
var pageContent = document.getElementById('contentwrapper');
2015-05-25 18:18:56 +00:00
var busyAnimIco;
2015-05-05 03:47:58 +00:00
// Select the proper icon
switch(type) {
case 'ok':
2015-05-25 18:18:56 +00:00
busyAnimIco = 'fa fa-check fa-4x';
break;
case 'fail':
2015-05-25 18:18:56 +00:00
busyAnimIco = 'fa fa-remove fa-4x';
break;
case 'busy':
default:
busyAnimIco = 'fa fa-refresh fa-spin fa-4x';
break;
}
2015-05-05 03:47:58 +00:00
// If requested to show the window build it
if(show) {
2015-05-05 03:47:58 +00:00
// Make sure it doesn't exist already
2015-05-25 18:18:56 +00:00
if(busyCont === null) {
2015-05-05 03:47:58 +00:00
// Container
var createBusyCont = document.createElement('div');
createBusyCont.className = 'ajax-busy';
createBusyCont.setAttribute('id', 'ajaxBusy');
2015-05-05 03:47:58 +00:00
// Inner box
var createBusyInner = document.createElement('div');
createBusyInner.className = 'ajax-inner';
createBusyCont.appendChild(createBusyInner);
2015-05-05 03:47:58 +00:00
// Action description
var createBusyMsg = document.createElement('h2');
createBusyMsg.setAttribute('id', 'ajaxStatus');
createBusyInner.appendChild(createBusyMsg);
2015-05-05 03:47:58 +00:00
// FontAwesome icon
var createBusySpin = document.createElement('div');
createBusySpin.setAttribute('id', 'ajaxAnimate');
createBusyInner.appendChild(createBusySpin);
2015-05-05 03:47:58 +00:00
// Append the element to the actual page
pageContent.appendChild(createBusyCont);
2015-05-05 03:47:58 +00:00
// Reassign the previously assigned variables
busyCont = document.getElementById('ajaxBusy');
busyStat = document.getElementById('ajaxStatus');
busyAnim = document.getElementById('ajaxAnimate');
2015-05-05 03:47:58 +00:00
} // If the container already exists just continue and update the elements
// Alter the icon
busyAnim.className = busyAnimIco;
2015-05-05 03:47:58 +00:00
// Change the message
2015-05-25 18:18:56 +00:00
busyStat.innerHTML = (message === null ? 'Unknown' : message);
2015-05-05 03:47:58 +00:00
} else { // If show is false remove the element...
// ...but just do nothing if the container doesn't exist
2015-05-25 18:18:56 +00:00
if(busyCont !== null) {
2015-05-05 03:47:58 +00:00
// Create the fadeout with a 10ms interval
var fadeOut = setInterval(function() {
2015-05-05 03:47:58 +00:00
// Set an opacity if it doesn't exist yet
2015-05-25 18:18:56 +00:00
if(busyCont.style.opacity === null || busyCont.style.opacity === "") {
busyCont.style.opacity = 1;
2015-05-25 18:18:56 +00:00
}
2015-05-05 03:47:58 +00:00
// If the value isn't 0 yet start subtract .1 from the opacity
if(busyCont.style.opacity > 0) {
2015-05-05 03:47:58 +00:00
2015-05-25 18:18:56 +00:00
busyCont.style.opacity = busyCont.style.opacity - 0.1;
2015-05-05 03:47:58 +00:00
} else { // When we've reached 0 remove the container element and clear the fadeout interval
removeId('ajaxBusy');
clearInterval(fadeOut);
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
}, 10);
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
}
2015-05-05 03:47:58 +00:00
// Making a post request using AJAX
function ajaxPost(url, data) {
2015-05-05 03:47:58 +00:00
// Create a new XMLHttpRequest
var req = new XMLHttpRequest();
2015-05-05 03:47:58 +00:00
// Open a post request
req.open("POST", url, false);
2015-05-05 03:47:58 +00:00
// Set the request header to a form
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
2015-05-05 03:47:58 +00:00
// Combine name and value with an = inbetween
var query = [];
2015-05-25 18:18:56 +00:00
for(var i in data) {
query.push(encodeURIComponent(i) +"="+ encodeURIComponent(data[i]));
2015-05-25 18:18:56 +00:00
}
2015-05-05 03:47:58 +00:00
// Join the array and submit the request
req.send(query.join("&"));
2015-05-05 03:47:58 +00:00
// If the HTTP resonse was 200 return the page
2015-05-25 18:18:56 +00:00
return (req.status === 200 ? req.responseText : null);
2015-05-05 03:47:58 +00:00
}
// Quickly building a form for god knows what reason
function generateForm(formId, formAttr, formData, appendTo) {
// Create form elements and assign ID
2015-05-25 18:18:56 +00:00
var i;
var form = document.createElement('form');
form.setAttribute('id', formId);
// Set additional attributes
2015-05-25 18:18:56 +00:00
if(formAttr !== null) {
for(i in formAttr) {
form.setAttribute(i, formAttr[i]);
2015-05-25 18:18:56 +00:00
}
}
// Generate input elements
2015-05-25 18:18:56 +00:00
for(i in formData) {
var disposableVar = document.createElement('input');
disposableVar.setAttribute('type', 'hidden');
disposableVar.setAttribute('name', i);
disposableVar.setAttribute('value', formData[i]);
form.appendChild(disposableVar);
2015-05-25 18:18:56 +00:00
}
// Append to another element if requested
2015-05-25 18:18:56 +00:00
if(appendTo !== null) {
document.getElementById(appendTo).appendChild(form);
2015-05-25 18:18:56 +00:00
}
// Return the completed form
return form;
}
2015-04-27 00:41:59 +00:00
// Enter substitute
function formEnterCatch(key, id) {
// 13 == Enter
if(key.which == 13) {
// Submit the form
document.getElementById(id).click();
// Return true if yeah
return true;
}
// Return false if not
return false;
}
// Submitting a form using an AJAX POST request
2015-05-25 18:18:56 +00:00
function submitPost(formId, busyView, msg, resetCaptchaOnFailure) {
// If requested display the busy thing
2015-05-25 18:18:56 +00:00
if(busyView) {
ajaxBusyView(true, msg, 'busy');
2015-05-25 18:18:56 +00:00
}
// Get form data
var form = document.getElementById(formId);
// Make sure the form id was proper and if not report an error
2015-05-25 18:18:56 +00:00
if(form === null) {
if(busyView) {
ajaxBusyView(true, 'Invalid Form ID, contact the administrator.');
setTimeout(function(){ajaxBusyView(false);}, 2000);
}
return;
}
// Make an object for the request parts
var requestParts = new Object();
// Get all children with a name attribute
var children = form.querySelectorAll('[name]');
// Sort children and make them ready for submission
for(var i in children) {
2015-05-25 18:18:56 +00:00
if(typeof children[i] == 'object') {
requestParts[children[i].name] = ((typeof children[i].type !== "undefined" && children[i].type.toLowerCase() == "checkbox") ? children[i].checked : children[i].value);
2015-05-25 18:18:56 +00:00
}
}
// Submit the AJAX request
var request = ajaxPost(form.action, requestParts).split('|');
// If using the busy view thing update the text displayed to the return of the request
2015-05-25 18:18:56 +00:00
if(busyView) {
ajaxBusyView(true, request[1], (request[2] == '1' ? 'ok' : 'fail'));
2015-05-25 18:18:56 +00:00
}
// If request reset the recaptcha on failure
if(resetCaptchaOnFailure && request[2] != '1') {
grecaptcha.reset();
}
setTimeout(function(){
2015-05-25 18:18:56 +00:00
if(busyView) {
ajaxBusyView(false);
2015-05-25 18:18:56 +00:00
}
if(request[2] == '1') {
window.location = request[3];
2015-05-25 18:18:56 +00:00
}
}, 2000);
return;
}
2015-05-05 03:47:58 +00:00
2015-05-25 18:18:56 +00:00
// Encode UTF-8
function utf8_encode(str) {
return unescape(encodeURIComponent(str));
}
// Decode UTF-8
function utf8_decode(str) {
return decodeURIComponent(escape(str));
}
// Calculate the amount of unique characters in a string
function uniqueChars(str) {
// Create storage array and count var
var usedChars = new Array();
var count = 0;
// Count the amount of unique characters
for(var i = 0; i < str.length; i++) {
// Check if we already counted this character
if(usedChars.indexOf(str[i]) == -1) {
// Push the character into the used array
usedChars.push(str[i]);
// Up the count
count++;
}
}
// Return the count
return count;
}
// Alternative for Math.log2() since it's still experimental
function log2(num) {
return Math.log(num) / Math.log(2);
}
// Calculate password entropy
function pwdEntropy(pwd) {
// Decode utf-8 chars
pwd = utf8_decode(pwd);
// Count the amount of unique characters in the password and calculate the entropy
return uniqueChars(pwd) * log2(256);
}
// Check if password is within the minimum entropy value
function checkPwdEntropy(pwd) {
return (pwdEntropy(pwd) >= sakuraVars.minPwdEntropy);
}
// Check the length of a string
function checkStringLength(str, min, max) {
// Get length of string
var len = str.length;
// Check if it meets the minimum
if(len < min)
return false;
// Check if it meets the maximum
if(len > max)
return false;
// If it passes both return true
return true;
}
// Validate email address formats
function validateEmail(email) {
// The regex
2015-06-19 19:42:53 +00:00
var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/;
// is of fix
2015-05-25 18:18:56 +00:00
// Test it (which returns true or false)
return re.test(email);
}
// Check registration variables
function registerVarCheck(id, mode, option) {
// Get the element we're working with
var input = document.getElementById(id);
var check = null;
// Use the proper mode
switch(mode) {
case 'confirmpw':
option = document.getElementById(option);
check = input.value === option.value;
break;
case 'password':
check = checkPwdEntropy(input.value);
break;
case 'email':
check = validateEmail(input.value);
break;
case 'username':
default:
check = checkStringLength(input.value, sakuraVars.minUserLen, sakuraVars.maxUserLen);
break;
}
if(input.className.indexOf(check ? 'green' : 'red') < 0) {
input.className = input.className + ' ' + (check ? 'green' : 'red');
}
if(input.className.indexOf(check ? 'red' : 'green') > 0) {
input.className = input.className.replace(check ? 'red' : 'green', '');
}
}
2015-05-05 03:47:58 +00:00
// Initialising the element parallax functionality
function initialiseParallax(id) {
// Assign the element to a variable
var parallax = document.getElementById(id);
// Set proper position values
parallax.style.top = '-2.5px';
parallax.style.bottom = '-2.5px';
parallax.style.left = '-2.5px';
parallax.style.right = '-2.5px';
// Add the event listener to the body element
document.addEventListener("mousemove", function(e) {
// Alter the position of the parallaxed element
parallax.style.top = convertParallaxPositionValue(e.clientY, true, false) + 'px';
parallax.style.bottom = convertParallaxPositionValue(e.clientY, true, true) + 'px';
parallax.style.left = convertParallaxPositionValue(e.clientX, false, false) + 'px';
parallax.style.right = convertParallaxPositionValue(e.clientX, false, true) + 'px';
});
}
// Converting the position value of the mouseover to a pixel value
function convertParallaxPositionValue(pos, dir, neg) {
// Get the body element
var body = document.getElementsByTagName('body')[0];
// Get percentage of current position
var position = (pos / (dir ? body.clientHeight : body.clientWidth)) * 100;
// If someone decided to fuck with the inputs reset it to 0%
2015-05-25 18:18:56 +00:00
if(position < 0 || position > 100) {
2015-05-05 03:47:58 +00:00
position = 0;
2015-05-25 18:18:56 +00:00
}
2015-05-05 03:47:58 +00:00
// Do the first maths
position = (position / (dir ? 25 : 20)) - 2.5;
// If the negative flag is set inverse the number
2015-05-25 18:18:56 +00:00
if(neg) {
2015-05-05 03:47:58 +00:00
position = -position;
2015-05-25 18:18:56 +00:00
}
2015-05-05 03:47:58 +00:00
// Subtract another 2.5 to make the element not go all over the place
position = position - 2.5;
// Return the proper position value
return position;
}
2015-05-24 22:06:53 +00:00
// Smooth scrolling
2015-05-25 18:18:56 +00:00
function scrollToTop() {
2015-05-24 22:06:53 +00:00
// Get the current position
2015-05-25 18:18:56 +00:00
var windowY = window.pageYOffset - 100;
// Move up
window.scrollTo(0, windowY);
// Keep executing this function till we're at the top
if(windowY + 1 > 0) {
setTimeout(function(){scrollToTop();}, 10);
}
2015-05-24 22:06:53 +00:00
}
2015-07-01 17:20:20 +00:00
// Formatting money
Number.prototype.formatMoney = function(c, d, t) {
var n = this,
c = isNaN(c = Math.abs(c)) ? 2 : c,
d = d == undefined ? "." : d,
t = t == undefined ? "," : t,
s = n < 0 ? "-" : "",
i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
};
2015-05-24 22:06:53 +00:00
// Event watcher for the scroll-to-top button
window.onscroll = function() {
// Assign the gotop button to a variable
var gotop = document.getElementById('gotop');
// If the vertical offset of the page is below 112px (just below the header) keep the button hidden
if(this.pageYOffset < 112) {
// Check if the "exit" is in the classes and if it isn't continue
if(gotop.className.indexOf('exit') < 0) {
// Replace the enter with exit (for the animation)
gotop.className = gotop.className.replace('enter', '');
gotop.className = gotop.className + ' exit';
// Check if hidden is set and if not continue
if(gotop.className.indexOf('hidden') < 0) {
// Set a timeout to add the hidden class after 600ms
setTimeout(function() {
gotop.className = gotop.className + ' hidden';
}, 600);
}
}
// Else show the button
} else if(this.pageYOffset > 112) {
// Check if enter is set
if(gotop.className.indexOf('enter') < 0) {
// Remove the hidden and exit classes and add the enter class
gotop.className = gotop.className.replace('hidden', '');
gotop.className = gotop.className.replace('exit', '');
gotop.className = gotop.className + ' enter';
}
}
};