ComunicWeb/assets/js/common/utils.js

845 lines
20 KiB
JavaScript

/**
* Utilities functions
*
* @author Pierre HUBERT
*/
/**
* Create a new HTML node
*
* @param {String} nodeType The type of the HTML node
* @param {HTMLElement} appendTo Optionnal, defines node on which the new node will be applied
* @return {HTMLElement} The newly created element
*/
function createElem(nodeType, appendTo){
var newElem = document.createElement(nodeType);
if(appendTo)
appendTo.appendChild(newElem);
//Return result
return newElem;
}
/**
* Create a new HTML node (version2)
*
* @param {CreateElem2Args} infos Informations about the HTML node to create
* @info {String} type The type of the new node
* @info {HTMLElement} appendTo HTML Element that will receive the new node
* @info {HTMLElement} insertBefore Insert before specified HTML element
* @info {HTMLElement} insertAsFirstChild Insert the new HTML Element as the first child of the specified element
* @info {String} class The class of the new element
* @info {String} id The ID of the new element
* @info {String} title The title of the new element
* @info {String} src The src attribute of the new element
* @info {String} href href attribute for the src element
* @info {String} internalHref Link to application page
* @info {string} name The name of the new element
* @info {String} elemType The type attribute of the new element
* @info {String} value The value of the new element
* @info {String} placeholder The placeholder of the new element
* @info {String} innerHTML Specify the html content of the newly created element
* @info {String} innerLang Specify the key of the lang to use to fill the element
* @info {String} innerHTMLprefix Specify prefix to add at the begining of the content of the element
* @info {boolean} disabled Set whether the field should be disabled or not (input only)
* @info {HTMLElement[]} children Children for the new object
* @info {Function} onclick
* @info {Function} ondblclick
* @return {HTMLElement} The newly created element
*/
function createElem2(infos){
var newElem = document.createElement(infos.type);
//Append to a specific element
if(infos.appendTo)
infos.appendTo.appendChild(newElem);
//Append before a specific element
if(infos.insertBefore)
infos.insertBefore.parentNode.insertBefore(newElem, infos.insertBefore);
//Append as the first child of an element
if(infos.insertAsFirstChild){
//Check if the element as already a child or not
if(infos.insertAsFirstChild.firstChild)
infos.insertAsFirstChild.insertBefore(newElem, infos.insertAsFirstChild.firstChild);
//Else we can just append the newly created element
else
infos.insertAsFirstChild.appendChild(newElem);
}
//Specify the class of the element
if(infos.class)
newElem.className = infos.class;
//Specify the ID of the element
if(infos.id)
newElem.id = infos.id;
//Specify the title of the new element
if(infos.title)
newElem.title = infos.title;
//Specify the source of the element
if(infos.src)
newElem.src = infos.src;
if(infos.href)
newElem.href = infos.href;
if(infos.internalHref){
newElem.addEventListener("click", function(e){
e.preventDefault();
openPage(infos.internalHref);
});
}
//Specify the name of the new element
if(infos.name)
newElem.name = infos.name;
//Specify element type
if(infos.elemType)
newElem.type = infos.elemType;
//Specify element value
if(infos.value)
newElem.value = infos.value;
//Specify element placeholder
if(infos.placeholder)
newElem.placeholder = infos.placeholder;
//Specify node content
if(infos.innerHTML)
newElem.innerHTML = infos.innerHTML;
if(infos.innerLang)
newElem.innerHTML = lang(infos.innerLang);
if(infos.innerHTMLprefix)
newElem.innerHTML = infos.innerHTMLprefix + newElem.innerHTML;
//Set field state
if(infos.disabled)
infos.disabled = true;
if(infos.children){
infos.children.forEach(function(i){
newElem.appendChild(i);
});
}
if(infos.onclick)
newElem.addEventListener("click", infos.onclick);
if(infos.ondblclick)
newElem.addEventListener("dblclick", infos.ondblclick);
//Return newly created element
return newElem;
}
/**
* Get an HTML element specified by an ID
*
* @param {String} nodeName The ID of the element
* @return {HTMLElement} The element / False for a failure
*/
function byId(nodeName){
return document.getElementById(nodeName);
}
/**
* Remove all nodes of a specified HTML element
*
* @param {HTMLElement} container The container to empty
* @return {Boolean} True for a success
*/
function emptyElem(container){
//Process each child
while(container.children.length > 0){
//Check if the child has subchild
if(container.children[0].children)
emptyElem(container.children[0]); //Remove them first
//Remove child
container.children[0].remove();
}
//Success
return true;
}
/**
* Delete all the content of an object
*
* @param {Object} object The object to clear
* @return {Boolean} True for a success
*/
function clearObject(object){
//Variable (for loop) is specific to this local scop
var i = 0;
//Process each node of the object
for(i in object){
if(!object[i])
continue;
//Check if the node is an object
if(object[i].toString() === "[object Object]"){
clearObject(object[i]); //Delete object content
}
//Delete node
delete object[i];
}
//Success
return true;
}
/**
* Check a given email address
*
* @param {String} emailAddress The email address to check
* @return {Boolean} True for a valid email address / false else
*/
function checkMail(emailAddress){
return (emailAddress.match(/^[a-zA-Z0-9_.]+@[a-zA-Z0-9-.]{1,}[.][a-zA-Z]{2,8}$/) === null ? false : true);
}
/**
* Create a formgroup element
*
* @param {Object} infos Informations about the formgroup element to create
* * @info {HTMLElement} target The target of the field
* * @info {String} label The label of the field
* * @info {string} name The name of the field
* * @info {String} placeholder The placeholder of the field
* * @info {Boolean} checked Defines if the fields has to be checked or not (checkbox/radio only)
* * @info {Boolean} multiple Defines if the fields can accept more than one response
* * @info {String} type The type of the field
* * @info {string} value The default value of the input
* * @info {boolean} disabled Set whether the field should be disabled or not
* * @info {string} additionalGroupClasses Additionnal form group class names
* @return {HTMLElement} The input
*/
function createFormGroup(infos){
//Check for default value
var value = infos.value ? infos.value : "";
//Check if the field has to be disabled
var disabled = infos.disabled;
//Create formgroup
var formGroup = createElem("div", infos.target);
formGroup.className = "form-group";
//Add optionnal classes if required
if(infos.additionalGroupClasses){
formGroup.className += " " + infos.additionalGroupClasses;
}
//Add label
var labelElem = createElem("label", formGroup);
//Treatement differs if it is a checkbox
if(infos.type == "checkbox"){
//Create checkbox
var input = createElem("input", labelElem) ;
input.type = "checkbox";
input.disabled = disabled;
//Check if input has to be checked by default
if(infos.checked){
if(infos.checked === true){
input.checked = "true";
}
}
//Add label value
var labelValue = createElem("span", labelElem);
labelValue.innerHTML = " "+infos.label;
//Enable iCheck
$(input).iCheck({
checkboxClass: 'icheckbox_flat-blue',
radioClass: 'iradio_flat-blue'
});
}
//In case of radio input
else if(infos.type == "radio"){
//Create radio
var input = createElem("input", labelElem) ;
input.type = "radio";
input.disabled = disabled;
if(infos.name)
input.name = infos.name;
if(infos.value)
input.value = infos.value;
//Check if input has to be checked by default
if(infos.checked){
if(infos.checked === true){
input.checked = "true";
}
}
//Add label value
var labelValue = createElem("span", labelElem);
labelValue.innerHTML = " "+infos.label;
//Enable iCheck
$(input).iCheck({
checkboxClass: 'icheckbox_flat-blue',
radioClass: 'iradio_flat-blue'
});
}
//Select2
else if(infos.type == "select2"){
//In case of select2 element
//Check for label
if(infos.label)
labelElem.innerHTML = infos.label;
else
labelElem.remove(); //Remove useless label element
//Create input
var input = createElem("select", formGroup);
input.style.width = "100%";
input.className = "form-control select2";
input.disabled = disabled;
if(infos.multiple) //For multiple changes
input.setAttribute("multiple", "multiple");
if(infos.placeholder) //Placeholder if required
input.setAttribute("data-placeholder", infos.placeholder);
}
//In case of textarea
else if(infos.type == "textarea"){
//Fill label value
if(infos.label)
labelElem.innerHTML = infos.label;
else
labelElem.remove(); //Remove useless label element
//Create textarea element
var input = createElem2({
appendTo: formGroup,
type: "textarea",
class: "form-control",
placeholder: infos.placeholder,
value: value,
disabled: disabled
});
}
else {
//Else continue the function as a normal input type
labelElem.innerHTML = infos.label;
//Create input group
var inputGroup = createElem("div", formGroup);
inputGroup.className = "input-group";
inputGroup.style.width = "100%";
//Create input
var input = createElem("input", inputGroup);
input.className = "form-control";
input.type = infos.type;
input.placeholder = infos.placeholder;
input.value = value;
input.disabled = disabled;
}
//Return input
return input;
}
/**
* Create a radio element
*
* @param {HTMLElement} target The target of the radio
* @param {string} name The name of the radio group
* @param {string} label The label of the radio
* @return {HTMLElement} The input element of the radio
*/
function create_radio(target, name, label){
//Container
var radioDiv = createElem2({
appendTo: target,
type: "div",
class: "radio icheck"
});
//Label
var radioLabel = createElem2({
appendTo: radioDiv,
type: "label"
});
//Input
var radioInput = createElem2({
appendTo: radioLabel,
type: "input",
name: name,
elemType: "radio"
});
//Add label
createElem2({
appendTo: radioLabel,
type: "span",
innerHTML: " "+ label
});
//Enable input
$(radioInput).iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue'
});
return radioInput;
}
/**
* Check if a string is valid and ready to be sent to be saved
*
* @param {String} value The input string to send
* @return {Boolean} True if the string is valid, false else
*/
function checkString(value){
//First, check string length
if(value.length < 3)
return false; //Lenght invalid
//Success, the string seems to be valid
return true;
}
/**
* Remove HTML carachters : < and >
*
* @param {String} input The string to change
* @return {String} The updated string
*/
function removeHtmlTags(input){
//Check if input string is empty
if(input == null || typeof input !== "string")
return "";
//Prepare update
var output = input;
//Replace opening braces
while(output.includes("<")){
//Replace an occurence
output = output.replace("<", "&lt;");
}
//Replace closing braces
while(output.includes(">")){
//Replace an occurence
output = output.replace(">", "&gt;");
}
//Return result
return output;
}
/**
* Replace all line break with paragraph tags
*
* @param {string} input Input string to convert
* @return {string} Generated string
*/
function lineBreakToPTags(input){
//Check if the string is empty
if(input == null || input == "")
return input;
//Change string
while(input.includes("\n"))
input = input.replace("\n", "</p><p>");
return "<p>"+input+"</p>";
}
/**
* Check a URL validity
*
* Source: https://gist.github.com/729294
*
* @param {string} url The URL to check
* @return {boolean} TRUE if the URL is valid
*/
function check_url(url){
var regex = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
return url.match(regex) == null ? false : true;
}
/**
* Add a space in an HTML element
*
* @param {HTMLElement} target The target element for the space
*/
function add_space(target){
createElem2({
appendTo: target,
type: "span",
innerHTML: "&nbsp;"
});
}
/**
* Create and append a new paragraph
*
* @param {HTMLElement} target The target for the new paragraph
* @param {String} content The new content for the paragraph
* @returns {HTMLElement} Generated element
*/
function add_p(target, content){
return createElem2({
appendTo: target,
type: "p",
innerHTML: content
});
}
/**
* Get the current absolute position bottom of the screen
*
* @return {number} The bottom on the screen
*/
function abs_height_bottom_screen(){
return window.scrollY + $(window).height();
}
/**
* Page URL update detection
*
* @source https://stackoverflow.com/a/1931090/3781411
*/
window.location.changed = function(e){};
(function() //create a scope so 'location' is not global
{
/*var m_loc = window.location.href;
const doCheckup = function()
{
if(m_loc != window.location.href)
{
m_loc = window.location.href;
window.location.changed(window.location);
}
};*/
window.addEventListener("popstate",
e => window.location.changed(window.location));
})();
/**
* jQuery special event that detects the deletion
* of a DOM element
*
* @source StackOverFlow answer from mtkopone
*/
(function($){
$.event.special.destroyed = {
remove: function(o){
if(o.handler)
o.handler();
}
}
})(jQuery);
/**
* Generate a new random string
*
* @param {number} length The length of the string ot generate
* @return {string} Generated string
*/
function random_string(length){
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst0123456789";
for(var i = 0; i < length; i++)
text += possible.charAt(Math.random() * possible.length);
return text;
}
/**
* Generate and return a new random identity image
*
* @return {string} Base64 - encoded new identity image
*/
function generateIdentImage() {
var hash = random_string(32);
var color = Math.random() * 240;
var color2 = Math.random() * 240;
var color3 = Math.random() * 240;
var options = {
foreground: [color, color2, color3, 255],
size: 130,
margin: 0.2,
format: 'png'
};
return new Identicon(hash, options).toString();
}
/**
* Turn a data URI into blob
*
* This function is based on Stoive answer at StackOverFlow question #4998908
*
* @param {string} dataURI The URI to process
* @return {Blob} generated blob
*/
function dataURItoBlob(dataURI){
//convert base64 / URLEncoded data component to raw binary data held in a string
var byteString;
if(dataURI.split(",")[0].indexOf("base64") >= 0)
byteString = atob(dataURI.split(",")[1]);
else
byteString = unescape(dataURI.split(",")[1]);
//Separate the out the mime component
var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
//Write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for(var i = 0; i < byteString.length; i++)
ia[i] = byteString.charCodeAt(i);
return new Blob([ia], {type: mimeString});
}
/**
* Satinize some HTML source code by removing all javascript event detectors
* from it
*
* @param {string} html The source code to update
* @return {string} Secured html
*/
function removeJavascriptEventsFromHTML(html){
//Check if the string to check is null (we will consider
//at safe in this case)
if(html == null)
return html;
//Search for unexceptable references
while(html.match(/on[a-zA-Z ]+=/i) != null){
var match = html.match(/on[a-zA-Z ]+=/i)[0];
html = html.replace(match, match.replace("on", "o<block></block>n"))
}
return html;
}
/**
* Get and return the DOM of a specified iframe
*
* @param {HTMLIFrameElement} iframe The iframe to process
* @return {HTMLDocument} DOM of the iframe
*/
function GetIframeDOM(iframe){
return iframe.contentWindow
? iframe.contentWindow.document
: iframe.contentDocument;
}
/**
* Initialize styles for a sceditor textarea
*
* @param {HTMLTextAreaElement} textarea Target textarea element that
* have sceditor initialized
*/
function ApplySceditorStyle(textarea){
//Get iframe DOM
var iframeDOM = GetIframeDOM(textarea.parentNode.getElementsByTagName("iframe")[0]);
//Apply stylesheets
document.querySelectorAll("link[rel='stylesheet']").forEach(function(entry){
//Skip the entry if it is disabled
if(entry.disabled)
return;
var elem = iframeDOM.createElement("link");
elem.rel = "stylesheet";
elem.href = entry.href;
iframeDOM.head.appendChild(elem);
});
//Apply new styles to body
iframeDOM.body.className += " sceditor-iframe-body";
}
/**
* Send a new javascript event
*
* @param {String} name The name of the event to create
* @param {Object} details Information about the event to create
*/
function SendEvent(name, details){
var event = new CustomEvent(name, {
detail: details,
bubbles: true,
cancelable: false
});
document.dispatchEvent(event);
}
/**
* Request full screen for an HTML element
*
* Note : this function must be called inside of an event of
* the user like a click, otherwise it will not work
*
* This function source code is based on this StackOverFlow Answer
* https://stackoverflow.com/a/32100295/3781411
*
* @param {HTMLElement} elem The element for which we want
* full screen
*/
function RequestFullScreen(element){
if (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
}
/**
* Check out whether Comunic has grabbed full screen or not
*
* @return {Boolean} TRUE if the window is in full screen / FALSE else
*/
function IsFullScreen(){
return document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement;
}
/**
* Check an emoji code
*
* @param s The emoji code to check
*/
function checkEmojiCode(s) {
return s.match(/^:[a-zA-Z0-9]+:$/) != null
}
/**
* Request user screen as stream
*
* @return {Promise<MediaStream>}
*/
function requestUserScreen() {
return new Promise((onSuccess, onFailure) => {
getScreenId(function (error, sourceId, screen_constraints) {
if(error != null)
return onFailure(error)
// error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome'
// sourceId == null || 'string' || 'firefox'
if(navigator.getDisplayMedia) {
navigator.getDisplayMedia(screen_constraints).then(onSuccess, onFailure);
}
else {
navigator.mediaDevices.getUserMedia(screen_constraints).then(onSuccess).catch(onFailure);
}
});
})
}
/**
* Rpad function
*
* @param {String} str The string
* @param {number} len Expected length
* @param {string} fill Fill character
*/
function rpad(str, len, fill) {
str = String(str)
fill = String(fill)
while(str.length < len)
str = fill + str
return str
}
/**
* Format file size to human readable format
*
* @param {number} size The size to format
*/
function fileSizeToHuman(size) {
return Math.round(ServerConfig.conf.conversation_files_max_size/(1000*1000)*100)/100 + "MB";
}