/* Pagelayer Pen editor */ var pagelayer_customColor = ["#000000", "#e60000", "#ff9900", "#ffff00", "#008a00", "#0066cc", "#9933ff", "#ffffff", "#facccc", "#ffebcc", "#ffffcc", "#cce8cc", "#cce0f5", "#ebd6ff", "#bbbbbb", "#f06666", "#ffc266", "#ffff66", "#66b966", "#66a3e0", "#c285ff", "#888888", "#a10000", "#b26b00", "#b2b200", "#006100", "#0047b2", "#6b24b2", "#444444", "#5c0000", "#663d00", "#666600", "#003700", "#002966", "#3d1466"]; var pagelayer_pen_sizeList = ['normal', 'x-small', 'small', 'medium', 'large', 'x-large']; var pagelayer_pen_lineHeight = ['0.9', '1', '1.5', '2.0', '2.5','3.0', '3.5', '4.0', '4.5', '5.0']; class PagelayerPen{ constructor(jEle, options) { var t = this; t.editor = jQuery(jEle); t.options = options; // Get the document of the element. It use to makes the plugin // compatible on iframes. t.doc = jEle.ownerDocument || document; t.tagToButton = {}; t.optionsCounter = 0; t.destroyEd = true; t.semantic = null; t.DEFAULT_SEMANTIC_MAP = { 'b': 'strong', 'i': 'em', 's': 'strike', //'strike': 'del', 'div': 'p' }; // Init editor t.addHandlers(); t.init(); } init(){ var t = this; // Init Editor t.editor.addClass('pagelayer-pen'); t.penHolder = t.addContainer(); t.addEvents(); } addHandlers(){ // TODO : Add for custom plugins // TODO remove all execCommands this.handlers = { bold:{ tag: 'STRONG', icon: '' }, italic:{ tag: 'EM', icon: '' }, underline:{ tag: 'U', icon: '' }, strike:{ tag: 'strike', fn: 'strikethrough', icon: '' }, h1:{ fn: 'formatBlock', icon: 'H1' }, h2:{ fn: 'formatBlock', icon: 'H2' }, h3:{ fn: 'formatBlock', icon: 'H3' }, h4:{ fn: 'formatBlock', icon: 'H4' }, h5:{ fn: 'formatBlock', icon: 'H5' }, h6:{ fn: 'formatBlock', icon: 'H6' }, p:{ fn: 'formatBlock', icon: '' }, blockquote:{ fn: 'formatBlock', icon: '' }, formating:{ fn: 'formatBlock', fixIcon: '' }, unorderedlist:{ tag: 'UL', fn: 'insertUnorderedList', icon: '' }, orderedlist:{ tag: 'OL', fn: 'insertOrderedList', icon: '' }, sub:{ tag: 'sub', fn: 'subscript', icon: '' }, super:{ tag: 'sup', fn: 'superscript', icon: '' }, link:{ fn: 'setLinkHandler', tag: 'a', icon: '', }, image:{ fn: 'imageBtnHandler', icon: '' }, align:{ style: 'text-align', fn: 'formatBlock', icon: { 'left': '', 'center': '', 'right': '', 'justify': '', } }, color:{ class: 'pagelayer-pen-color-picker', style: 'color', fn: 'commandHandler', fixIcon: ' ', buildBtn : 'buildColorBtnHandler', default : pagelayer_customColor, customInpute: true }, background:{ class: 'pagelayer-pen-color-picker', style: 'background-color', fn: 'commandHandler', fixIcon: ' ', buildBtn: 'buildColorBtnHandler', default : pagelayer_customColor, customInpute: true }, size:{ class: 'pagelayer-pen-size-picker', style: 'font-size', fn: 'commandHandler', default : pagelayer_pen_sizeList, customInpute: true }, lineheight:{ style: 'line-height', fn: 'commandHandler', fixIcon: '', default : pagelayer_pen_lineHeight, customInpute: true }, font:{ style: 'font-family', fn: 'commandHandler', fixIcon: '', default : pagelayer_fonts, buildBtn : 'buildfontBtnHandler', }, viewHTML:{ fn: 'viewHTMLBtnHandler', icon: '' }, removeformat:{ icon: '' } } } addContainer(className){ className = className || false; // Add Container var container = jQuery('.pagelayer-pen-holder'); if(container.length < 1){ jQuery('body').append('
'); container = jQuery('.pagelayer-pen-holder'); } if(!className){ return container; } if(container.find('.'+className).length < 1){ container.append('
'); } return container.find('.'+className); } addToolbar(){ // Add Toolbar var t = this; var groups = t.options.toolbar; var toolbar = t.toolbar = t.addContainer('pagelayer-pen-toolbar'); // Make it empty toolbar.empty(); if (!Array.isArray(groups[0])) { groups = [groups]; } var addButton = function(container, format, value){ var btn = t.handlers[format]; var icon = ''; if('icon' in btn){ var _icon = btn['icon']; if(typeof _icon == 'object' && !pagelayer_empty(_icon[value])){ icon = _icon[value]; }else if(typeof icon == 'string'){ icon = _icon; } } var input = document.createElement('button'); input.setAttribute('type', 'button'); input.setAttribute('data-format', format); input.classList.add('pagelayer-pen-' + format); if('class' in btn){ input.classList.add(btn['class']); } if( pagelayer_empty(value) && 'default' in btn ){ value = btn['default']; } input.innerHTML = icon; if(value != null) { input.value = value; } container.appendChild(input); } var createoption = function(val, lang, type){ type = type || ''; var lang = pagelayer_empty(lang) ? 'Default' : lang; return ''; } var addSelect = function(container, format, values) { var input = document.createElement('select'); input.classList.add('pagelayer-pen-' + format); if('class' in t.handlers[format]){ input.classList.add(t.handlers[format]['class']); } input.setAttribute('data-format', format); if( pagelayer_empty(values) && 'default' in t.handlers[format] ){ values = t.handlers[format]['default']; } for(var kk in values){ var options = ''; var value = values[kk]; if(typeof value == 'object') { if(kk != 'default'){ options += ''; } for(y in value){ options += createoption((jQuery.isNumeric(y) ? value[y] : x), value[y], kk); } }else if(value !== false) { options += createoption(value, value); } else { options += createoption('', ''); } jQuery(input).append(options); } container.appendChild(input); } groups.forEach(function(controls){ var group = document.createElement('span'); group.classList.add('pagelayer-pen-formats'); controls.forEach(function (control){ var format = control; if(typeof control === 'object'){ format = Object.keys(control)[0]; } if( pagelayer_empty(t.handlers[format]) ){ return; } if( typeof control === 'string' ){ addButton(group, control); } else { var value = control[format]; if (Array.isArray(value)) { addSelect(group, format, value); } else { addButton(group, format, value); } } var btn = t.handlers[format]; t.tagToButton[(btn.tag || btn.style || format).toLowerCase()] = format; }); // TODO skip if format is not exist toolbar[0].appendChild(group); }); toolbar.find('button').on('click', function(){ var bEle = jQuery(this); var format = bEle.data('format'); if(! format in t.handlers){ return; } var btn = t.handlers[format]; t.currentFormat = format; t.execCmd(btn.fn || format, btn.param || format, btn.forceCss); }); toolbar.find('select').on('change', function(e){ var bEle = jQuery(this); var format = bEle.data('format'); var val = bEle.val(); if(! format in t.handlers){ return; } var btn = t.handlers[format]; t.currentFormat = format; t.execCmd(btn.fn || format, val, btn.forceCss); }); toolbar.find('select').each(function(){ var format = jQuery(this).data('format'); if('buildBtn' in t.handlers[format]){ try{ t[t.handlers[format]['buildBtn']](this); }catch(e){ try{ t.handlers[format]['buildBtn'](this); }catch(e2){ t.buildDropdown(this); } } return true; } t.buildDropdown(this); }); // Add close button toolbar.append(''); // Hide editor on click close tool handler toolbar.find('.pagelayer-pen-close').on('mousedown', function(e){ //e.preventDefault(); t.destroyEd = true; t.editor.trigger('blur'); }); } execCmd(cmd, param, forceCss, skipPen){ var t = this; skipPen = !!skipPen || ''; if(cmd !== 'dropdown'){ t.focus(); t.restoreRange(); } try{ document.execCommand('styleWithCSS', false, forceCss || false); }catch(c){} try{ t[cmd + skipPen](param); }catch(c){ try{ cmd(param); }catch(e2){ if(cmd === 'insertHorizontalRule'){ param = undefined; }else if (cmd === 'formatBlock'){ // TODO: check for && t.isIE param = '<' + param + '>'; } document.execCommand(cmd, false, param); t.semanticCode(); t.restoreRange(); } } if(cmd !== 'dropdown'){ t.updateButtonStatus(); t.editor.trigger('input'); } } commandHandler(value){ var t = this; var format = t.currentFormat; if( pagelayer_empty(format) ){ return; } var btn = t.handlers[format]; var sel = window.getSelection(); var text = t.range.commonAncestorContainer; var selectedText = t.range.cloneContents(); selectedText = jQuery('
').append(selectedText).html(); // Also select the tag if(text.nodeType === Node.TEXT_NODE){ text = text.parentNode; } if (text.innerHTML === selectedText && text != t.editor[0]) { var ele = jQuery(text); if('tag' in btn){ // Replace tag }else if('style' in btn){ var style = {}; style[btn.style] = value; ele.css(style); }else if('atts' in btn){ // Add attribute or toggle the element } } else { // TODO for toggle tags and add tags var html = jQuery('' + selectedText + ''); // Remove style from all childrend var style = {}; style[btn.style] = ''; html.find('[style]').css(style); // TODO: remove span element that have no atts var node = html[0]; var firstInsertedNode = node.firstChild; var lastInsertedNode = node.lastChild; t.range.deleteContents(); t.range.insertNode(node); if(firstInsertedNode) { t.range.setStartBefore(firstInsertedNode); t.range.setEndAfter(lastInsertedNode); } // Is previous element empty? var prev = jQuery(node).prev(); if( prev.length > 0 && prev.is(':empty') ){ prev.remove(); } } sel.removeAllRanges(); sel.addRange(t.range); } formatBlock(value){ var t = this, format = t.currentFormat, btn = t.handlers[format], startNode = t.range.startContainer, endNode = t.range.endContainer; if( startNode.nodeType == Node.TEXT_NODE && startNode.parentNode != t.editor[0] ){ startNode = startNode.parentNode; } if( endNode.nodeType == Node.TEXT_NODE && endNode.parentNode != t.editor[0] ){ endNode = endNode.parentNode; } // TODO: only for seleced content // Wrap text nodes in span for easier processing t.editor.contents().filter(function () { return this.nodeType === 3 && this.nodeValue.trim().length > 0; }).wrap(''); var isLineEnd = function(lEle){ return lEle == null || lEle.nodeName == 'BR' || t.isline(lEle); } var wrapLine = function(pLine){ var pLine = jQuery(pLine), lineFele, lineEele, finalP; // Get Parent Element if(pLine.parentsUntil(t.editor).length > 0){ pLine = pLine.parentsUntil(t.editor).last(); } if(t.isline(pLine)){ return pLine; } // Get line first element if(isLineEnd(pLine[0].previousSibling)){ lineFele = pLine; }else{ lineFele = pLine.prevAll().filter(function(){ return isLineEnd(this.previousSibling); }).first(); } // Get line last element if(isLineEnd(lineFele[0].nextSibling)){ lineEele = lineFele; }else{ lineEele = lineFele.nextAll().filter(function(){ return isLineEnd(this.nextSibling); }).first(); } // Wrap all with p tag if(lineFele.is(lineEele)){ finalP = lineFele.wrap('

').parent() }else{ finalP = lineFele.nextUntil(lineEele.next()).addBack().wrapAll('

').parent(); } finalP.next('br').remove(); return finalP; } // Get start block lavel elements var $sNode = jQuery(t.blockNode(startNode)); if($sNode.is(t.editor)){ $sNode = wrapLine(startNode); } var $eNode = jQuery(t.blockNode(endNode)); if($eNode.is(t.editor)){ $eNode = wrapLine(endNode); } var $oldEle = $sNode; if(! $sNode.is($eNode) ){ var findEnd = false; var addElement = function(addEle){ if(addEle[0].nodeName == 'UL' || addEle[0].nodeName == 'OL') { addEle.children().each(function(){ $oldEle = $oldEle.add(jQuery(this)); }); return; } $oldEle = $oldEle.add(addEle); } var wrapAllEle = function(nextEle){ if(nextEle.is($eNode) || nextEle.find($eNode).length > 0){ findEnd = true; return; } if(nextEle.length < 1){ return; } if(!t.isline(nextEle[0])){ nextEle = wrapLine(nextEle); } addElement(nextEle); wrapAllEle( nextEle.next() ); } wrapAllEle($sNode.next()); // Is start Element have a another parent var pars = $sNode.parentsUntil(t.editor); pars.each(function(){ var $par = jQuery(this); wrapAllEle($par.next()); }); if( pars.length > 0 ){ $sNode = pars.last(); } var nextEnd = $sNode.nextAll().filter(function(){ return jQuery(this).is($eNode) || jQuery(this).find($eNode).length > 0; }).first(); // Add elements if( nextEnd.length > 0 ){ var $nextEle = $sNode.nextUntil(nextEnd); $nextEle.each(function(){ var ulEle = jQuery(this); if($oldEle.has(ulEle)) return; addElement(ulEle); }); } // Add end element if(nextEnd.length > 0 && !nextEnd.is($eNode) && (nextEnd[0].nodeName == 'UL' || nextEnd[0].nodeName == 'OL')){ nextEnd.children().each(function(){ var li = jQuery(this); $oldEle = $oldEle.add(li); if(li.is($eNode) || li.find($eNode).length > 0) return false; }); }else{ $oldEle = $oldEle.add($eNode); } } if('style' in btn){ var style = {}; style[btn.style] = value; $oldEle.css(style); }else if('atts' in btn){ // Add attribute or toggle the element var attr = {}; attr[btn.atts] = value; $oldEle.attr(attr); }else{ // Replace tag var tag = value.toLowerCase(); // need to find all block ele and replace this $oldEle.each( function(){ var $cEle = jQuery(this); if($cEle.is(t.editor)){ return; } // Is List element if($cEle.css('display') == 'list-item'){ if( t.isline($cEle[0].firstChild)){ $cEle.children().each(function(){ var liChild = jQuery(this); if(t.isline(liChild[0])){ t.replaceTag(liChild, tag, true); return; } // TODO: Check and need to correct liChild.wrap('<' + tag + '/>'); liChild.next('br').remove(); }); return } $cEle.contents().wrapAll('<' + tag + '/>'); return; } t.replaceTag($cEle, tag, true); }); } // Get rid of pen temporary span's jQuery('[data-pts]', t.editor).contents().unwrap(); t.semanticCode(); t.restoreRange(); } blockNode( node ){ var t = this; while( !t.isline(node) && node != t.editor[0] ) { node = node.parentNode; } return node; } isline(node){ if (node.nodeType !== Node.ELEMENT_NODE) return false; if (node.childNodes.length === 0) return false; // Exclude embed blocks var style = window.getComputedStyle(node); return ['block', 'list-item'].indexOf(style.display) > -1; } replaceTag(ele, tag, copyAttr){ ele.wrap('<' + tag + '/>'); var par = ele.parent(); if(copyAttr){ jQuery.each(ele.prop('attributes'), function () { par.attr(this.name, this.value); }); } ele.contents().unwrap(); return par; } semanticCode(){ var t = this; t.semanticTag('b'); t.semanticTag('i'); t.semanticTag('s'); t.semanticTag('strike'); t.semanticTag('div', true); } semanticTag(oldTag, copyAttributes){ var t = this; var newTag; if(t.semantic != null && typeof t.semantic === 'object' && t.semantic.hasOwnProperty(oldTag)){ newTag = t.semantic[oldTag]; } else if (t.DEFAULT_SEMANTIC_MAP.hasOwnProperty(oldTag)) { newTag = t.DEFAULT_SEMANTIC_MAP[oldTag]; } else { return; } jQuery(oldTag, t.editor).each(function () { var $oldTag = jQuery(this); if($oldTag.contents().length === 0) { return false; } t.replaceTag($oldTag, newTag, copyAttributes); }); } addEvents(){ // Add Events var t = this, editor = t.editor, ctrl = false, debounceButtonStatus; var showToolBar = function(){ var jEle = t.penHolder.children(':visible'); if(jEle.length < 1){ jEle = t.toolbar; } t.showPen(jEle); }; // Save rage editor.on('focusout', function(e){ if(t.destroyEd){ t.editor.removeClass('pagelayer-pen-focused'); t.range = null; return; } t.saveRange(); }); // Prevent to hide toolbar t.penHolder.on('mousedown', function(e){ // TODO: taget only require Element t.destroyEd = false; }); // On editor blur editor.on('blur', function(){ if(!t.destroyEd){ return; } t.destroy(); }); editor.on('keydown', function(){ t.penHolder.hide(); }); editor.on('mousedown', function(){ if(t.editor.attr('contenteditable') == 'true'){ t.showPen(); } }); editor.on('mouseup keyup keydown', function(e){ if ((!e.ctrlKey && !e.metaKey) || e.altKey) { setTimeout(function () { // "hold on" to the ctrl key for 50ms ctrl = false; }, 50); } clearTimeout(debounceButtonStatus); debounceButtonStatus = setTimeout(function () { t.updateButtonStatus(); }, 50); }); // Set focus on editor editor.on('click', function(e){ if(t.editor.hasClass('pagelayer-pen-focused')){ return; } t.editor.attr('contenteditable', 'true'); t.editor.focus(); }); // Set focus on editor editor.on('focus', function(){ t.destroyEd = true; t.addToolbar(); t.showPen(); t.editor.addClass('pagelayer-pen-focused'); jQuery(window).unbind('scroll.penToobar'); jQuery(window).on('scroll.penToobar', showToolBar); jQuery(document).unbind('mousemove.penToobar'); jQuery(document).on('mousemove.penToobar', showToolBar); }); t.semanticCode(); } destroy(){ var t = this; //t.editor.attr('contenteditable', ''); t.penHolder.hide(); // Removing event listeners jQuery(document).unbind('mousemove.penToobar'); jQuery(window).unbind('scroll.penToobar'); } hasFocus(){ var t = this; return ( t.doc.activeElement === t.editor || t.contains( t.editor[0], t.doc.activeElement) ); } contains(parent, descendant) { try { // Firefox inserts inaccessible nodes around video elements descendant.parentNode; // eslint-disable-line no-unused-expressions } catch (e) { return false; } return parent.contains(descendant); } saveRange(){ var t = this, selection = t.doc.getSelection(); t.range = null; if (!selection || !selection.rangeCount) { return; } var savedRange = t.range = selection.getRangeAt(0), range = t.doc.createRange(), rangeStart; range.selectNodeContents(t.editor[0]); range.setEnd(savedRange.startContainer, savedRange.startOffset); rangeStart = (range + '').length; t.metaRange = { start: rangeStart, end: rangeStart + (savedRange + '').length }; } restoreRange(){ var t = this, metaRange = t.metaRange, savedRange = t.range, selection = t.doc.getSelection(), range; if(!savedRange){ return; } if(metaRange && metaRange.start !== metaRange.end){ // Algorithm from http://jsfiddle.net/WeWy7/3/ var charIndex = 0, nodeStack = [t.editor[0]], node, foundStart = false, stop = false; range = t.doc.createRange(); while(!stop && (node = nodeStack.pop())){ if (node.nodeType === 3){ var nextCharIndex = charIndex + node.length; if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) { range.setStart(node, metaRange.start - charIndex); foundStart = true; } if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) { range.setEnd(node, metaRange.end - charIndex); stop = true; } charIndex = nextCharIndex; } else { var cn = node.childNodes, i = cn.length; while (i > 0) { i -= 1; nodeStack.push(cn[i]); } } } } selection.removeAllRanges(); selection.addRange(range || savedRange); } getRange(){ var t = this; var selection = t.doc.getSelection(); if (selection == null || selection.rangeCount <= 0) return null; var range = selection.getRangeAt(0); if(range == null) return null; return range; } getRangeText(range){ return range + ''; } focus(){ var t = this; if(t.hasFocus()) return; t.editor.click(); t.editor.focus(); t.restoreRange(); } getBounds(range){ var rect = range.getBoundingClientRect(); return { bottom: rect.top + rect.height, height: rect.height, left: rect.left, right: rect.right, top: rect.top, width: 0 }; } showPen(jEle){ var t = this; jEle = jEle || jQuery(t.toolbar); var toolBar = jQuery(t.penHolder); var tooltipHeight = parseInt(toolBar.css('height')); var range = null; if(! t.hasFocus() && t.range != null){ range = t.range; }else{ range = t.getRange(); } if(range == null){ toolBar.hide(); return; } // Set left of toolbar var editorOffset = t.editor[0].getBoundingClientRect(); var editorTop = editorOffset.top; var editorLeft = editorOffset.left; var editorbottom = editorTop + editorOffset.height - tooltipHeight; var toolBarTop = editorTop - 10; var bound = t.getBounds(range); if(bound.height == 0 && bound.top == 0 && bound.left == 0){ toolBar.hide(); return; } var boundTop = bound.top - 15; // Set top of toolbar if( boundTop - tooltipHeight < 0 && bound.bottom > -5){ toolBarTop = bound.bottom + tooltipHeight + 15; }else if( editorbottom - 30 < 0 ){ toolBarTop = editorbottom + 20; }else if( toolBarTop - tooltipHeight < 0 ){ toolBarTop = tooltipHeight + 10; } // Show Toolbar toolBar.children().hide(); toolBar.show(); jEle.show(); // Set top of toolbar toolBar.css('top', toolBarTop); // Set left of toobar var docW = jQuery(window).width() - 10; var toolW = toolBar.width(); var edW = t.editor.width(); if(toolW > edW){ editorLeft = editorLeft - (toolW - edW) / 2 } toolBar.css('left', editorLeft+'px'); var tooltipLeft = toolBar.offset().left; if(tooltipLeft < 0){ toolBar.css('left', '1px'); } var toolRight = tooltipLeft + toolW; if(docW < toolRight){ toolBar.css('left', tooltipLeft - (toolRight - docW)+'px'); } } getContent(){ var editor = this.editor; var html = editor.html(); return html; } setContent(html){ var t = this; html = html || ''; t.editor.html(html); t.editor.trigger('input'); } updateButtonStatus(){ var t = this, toolbar = jQuery(t.toolbar), tags = t.getTagsRecursive(t.doc.getSelection().focusNode), activeClasses = 'pagelayer-pen-active'; jQuery('.' + activeClasses, toolbar).removeClass(activeClasses); jQuery.each(tags, function (i, tag){ var btnName; if(pagelayer_is_string(tag)){ btnName = t.tagToButton[tag.toLowerCase()]; }else{ btnName = t.tagToButton[Object.keys(tag)[0].toLowerCase()] } var $btn = jQuery('[data-format="'+btnName+'"]', toolbar); if($btn.length < 1){ return; } if($btn.find('.pagelayer-pen-picker-label').length > 0){ $btn.find('.pagelayer-pen-picker-label').addClass(activeClasses); return; } $btn.addClass(activeClasses); }); } getTagsRecursive(element, tags) { var t = this; var jEle = jQuery(element); tags = tags || (element && element.tagName ? [element.tagName] : []); if (element && element.parentNode) { element = element.parentNode; } else { return tags; } var tag = element.tagName; // Is this editor if (tag === 'DIV') { return tags; } // TODO: for all block element if (tag === 'P' && element.style.textAlign !== '') { tags.push(element.style.textAlign); } jQuery.each(t.tagHandlers, function (i, tagHandler) { tags = tags.concat(tagHandler(element, t)); }); tags.push(tag); var styles = jEle.attr('style'); if(!pagelayer_empty(styles)){ var styles = styles.split(';'); jQuery.each(styles, function(i, style){ style = style.split(':'); var ss = String(style[0]).trim(); var vv = String(style[1]).trim(); if(pagelayer_empty(ss) || ss in tags && !pagelayer_empty(tags[ss])){ return; } var obj = {}; obj[ss] = vv; tags.push(obj); }); } return t.getTagsRecursive(element, tags).filter(function (tag) { return tag != null; }); } buildDropdown(select){ var t = this; var fixIcon = ''; select = jQuery(select); var format = select.data('format'); var selAtts = ''; var options = ''; var optId = `pagelayer-pen-picker-options-${t.optionsCounter}`; t.optionsCounter += 1; Array.from(select[0].attributes).forEach(item => { selAtts += ' '+item.name+'="'+ item.value +'"'; }); Array.from(select[0].options).forEach(option => { var attrs = ''; var val = ''; var itemInner = ''; if(option.hasAttribute('value')){ val = option.getAttribute('value'); attrs += ' data-value="'+val+'"'; } if(option.textContent){ attrs += ' data-label="'+option.textContent+'"'; } // Set icon if('icon' in t.handlers[format] && typeof t.handlers[format]['icon'] == 'object' && !pagelayer_empty(t.handlers[format]['icon'][val])){ itemInner = t.handlers[format]['icon'][val]; } options += `${itemInner}`; }); if('fixIcon' in t.handlers[format]){ fixIcon = t.handlers[format]['fixIcon']; } var customInpute = ''; if('customInpute' in t.handlers[format] && !pagelayer_empty(t.handlers[format]['customInpute'])){ customInpute = ''; } var container = jQuery(` `); container.addClass('pagelayer-pen-picker'); select.before(container); select.hide(); var close = function(cEle){ cEle.removeClass('pagelayer-pen-expanded'); cEle.find('.pagelayer-pen-picker-label').attr('aria-expanded', 'false'); cEle.find('.pagelayer-pen-picker-options').attr('aria-hidden', 'true'); } var selectItem = function(item, trigger = false){ var selected = container.find('.pagelayer-pen-selected'); var label = container.find('.pagelayer-pen-picker-label'); var val = ''; if (item === selected) return; if (selected != null) { selected.removeClass('pagelayer-pen-selected'); } if(item == null) return; item.classList.add('pagelayer-pen-selected'); select.selectedIndex = Array.from(item.parentNode.children).indexOf( item, ); if (item.hasAttribute('data-value')) { val = item.getAttribute('data-value'); label.attr('data-value', val); } else { label.attr('data-value', val); } if (item.hasAttribute('data-label')) { label.attr('data-label', item.getAttribute('data-label')); } else { label.attr('data-label', ''); } if(!fixIcon){ label.html(item.innerHTML); } if(trigger) { select.val(val); select.trigger('change'); close(container); } } var toggleAriaAttribute = function(element, attribute) { element.setAttribute( attribute, !(element.getAttribute(attribute) === 'true'), ); } var togglePicker = function() { container.toggleClass('pagelayer-pen-expanded'); // Toggle aria-expanded and aria-hidden to make the picker accessible toggleAriaAttribute(container.find('.pagelayer-pen-picker-label')[0], 'aria-expanded'); toggleAriaAttribute(container.find('.pagelayer-pen-picker-options')[0], 'aria-hidden'); } container.find('.pagelayer-pen-picker-item').on('click', function(){ selectItem(this, true); close(container); }); container.find('.pagelayer-pen-picker-label').on('click', function(){ togglePicker(); }); container.find('.pagelayer-pen-custom-input').on('focusout keydown', function(e){ if(e.type == 'keydown' && e.keyCode != 13){ return; } e.preventDefault(); var val = jQuery(this).val(); if(pagelayer_empty(val)){ return; } var opt = select.find('option.pagelayer-pen-custom-value'); if(opt.length < 1){ select.append(''); opt = select.find('option.pagelayer-pen-custom-value'); } opt.val(val); select.val(val); select.trigger('change'); close(container); }); jQuery(t.toolbar).on('mousedown', function(e){ var tEle = jQuery(this); var target = jQuery(e.target); var tPicker = target.closest('.pagelayer-pen-picker'); if(target.closest('.pagelayer-pen-picker-item').length > 0) return; tEle.find('.pagelayer-pen-picker.pagelayer-pen-expanded').each(function(){ var picker = jQuery(this); if(tPicker.length > 0 && tPicker.is(picker))return; close(picker); }); }); // TODO need to correct this function update the select container.on('update', function(){ var item = container.find('.pagelayer-pen-selected'); if(item.length < 1){ item = container.find('.pagelayer-pen-picker-item').first(); } selectItem(item[0]); }); container.trigger('update'); return container; } buildColorBtnHandler(item){ var t = this; var select = t.buildDropdown(item); var format = select.data('format'); // Set color select.find('.pagelayer-pen-picker-item').each(function(){ var opt = jQuery(this); var color = opt.data('value'); opt.css({'background': color}); // TODO remove this and add on selecttion opt.on('click', function(){ if(format == 'color'){ opt.closest('.pagelayer-pen-picker-label').css({'text-color': color}); }else{ opt.closest('.pagelayer-pen-picker-label').css({'background-color': color}); } }); }); } buildfontBtnHandler(item){ var t = this; var select = t.buildDropdown(item); jQuery(item).on('change', function(){ pagelayer_link_font_family(jQuery(this)); }); } setLinkHandler(){ var t = this, documentSelection = t.doc.getSelection(), node = documentSelection.focusNode, text = new XMLSerializer().serializeToString(documentSelection.getRangeAt(0).cloneContents()), url = '', linkBtn = 'Link', unlinkBtn = 'Cancel'; while (['A', 'DIV'].indexOf(node.nodeName) < 0) { node = node.parentNode; } if(node && node.nodeName === 'A'){ var $a = jQuery(node); url = $a.attr('href'); } if(!pagelayer_empty(url)){ linkBtn = 'Update'; unlinkBtn = 'Unlink'; } t.saveRange(); var tooltip = this.addContainer('pagelayer-pen-link-tooltip'); t.linkTooltip = tooltip; var html = ''+linkBtn+''+unlinkBtn+''; tooltip.html(html); var input = tooltip.find('input[name="url"]'); // Keep saving old range var metaRange = t.metaRange; var savedRange = t.range; var restoreRange = function(){ t.metaRange = metaRange; t.range = savedRange; t.restoreRange(); } t.linkTooltip.find('.pagelayer-pen-link-btn').on('click', function(){ var url = input.val(); restoreRange(); t.execCmd('createLink', url, true ); t.editor.trigger('input'); t.showPen(); }); t.linkTooltip.find('.pagelayer-pen-unlink-btn').on('click', function(){ restoreRange(); if(unlinkBtn == 'Unlink'){ t.execCmd('unlink', undefined, undefined, true); } t.showPen(); }); t.showPen(t.linkTooltip); } imageBtnHandler(){ var t = this; t.destroyEd = false; t.destroy(); var frame = pagelayer_select_frame('image'); // On select update the stuff frame.on({'select': function(){ var state = frame.state(); var url = '', alt = '', id = ''; // External URL if('props' in state){ url = state.props.attributes.url; alt = state.props.attributes.alt; // Internal from gallery }else{ var attachment = frame.state().get('selection').first().toJSON(); //console.log(attachment); // Set the new and URL url = attachment.url; alt = attachment.alt; id = attachment.id; } t.editor.click(); t.restoreRange(); t.execCmd('insertImage', url, false, true); var $img = jQuery('img[src="' + url + '"]:not([alt])', t.editor); $img.attr('alt', alt); $img.attr('pl-media-id', id); } }); frame.open(); } viewHTMLBtnHandler(param){ var t = this; var html = t.getContent(); t.destroyEd = false; t.destroy(); // Add Container var HTMLviewer = jQuery('.pagelayer-pen-html-viewer'); if(HTMLviewer.length < 1){ jQuery('body').append('

'+ '
'+ ''+ '
'+ ''+ ''+ '
'+ '
'+ '
'); HTMLviewer = jQuery('.pagelayer-pen-html-viewer'); } HTMLviewer.find('.pagelayer-pen-html-area').val(html); HTMLviewer.show(); HTMLviewer.find('.pagelayer-pen-html-btn-update').unbind('click'); HTMLviewer.find('.pagelayer-pen-html-btn-update').on('click', function(){ var html = HTMLviewer.find('.pagelayer-pen-html-area').val(); t.range = null; t.editor.click(); t.setContent(html); t.editor.trigger('focus'); HTMLviewer.hide(); }); HTMLviewer.find('.pagelayer-pen-html-btn-cancel').unbind('click'); HTMLviewer.find('.pagelayer-pen-html-btn-cancel').on('click', function(){ t.editor.click(); t.focus(); HTMLviewer.hide(); }); } } Гемблинг предложения в виртуальном казино с вознаграждениями и спецпредложениями - Law Analysis with Rahul

Гемблинг предложения в виртуальном казино с вознаграждениями и спецпредложениями

Гемблинг предложения в виртуальном казино с вознаграждениями и спецпредложениями

Главный ресурс игорного заведения – онлайн-платформа, на которой обеспечивается вход ко каждой из возможностям игрового сервиса. Пользователи игрового ресурса вулкан смогут крутить слоты, активировать бонусы, участвовать в турнирах и выводить деньги. Полный функциональность предоставлен лишь зарегистрированным участникам клуба вулкан. По этой основе регистрация аккаунта – основной шаг на пути к полноценному выбору гемблинговых предложений. Зарегистрировать аккаунтную регистрацию сможет любой посетитель, что подходит под условиям ответственного и безопасного азартных игр.

Каким способом создать аккаунт на главном сайте?

Главное правило доступа к азартным играм на реальные средства – соответствие разрешённых возрастных норм. Сыграть в онлайн гэмблинге с бонусами и выигрышами разрешается с совершеннолетнего возраста. Возрастное соответствие пользователя устанавливается при идентификации. После удачной идентификации можно не только развлекаться в сети на реальные средства, но и снимать заработанные финансы.

Когда пользователь перешагнул совершеннолетнего рубежа, ему ничто не воспрепятствует открыть аккаунт. Пользователю главного веб-сайта казино необходимо зарегистрировать рабочий e-mail и действующий номер телефона. При отсутствии данных контактных опознавателей создать аккаунт невозможно. Чтобы зарегистрировать учетную запись, необходимо:

  1. Зайти на главную страницу официального сайта казино онлайн.
  2. Кликнуть иконку «Регистрация».
  3. Подобрать метод регистрации аккаунта.
  4. Заполнить опросные сведения.
  5. Введите пароль.
  6. Согласиться с условиями игрового клуба.

Способ авторизации выбирается в зависимости от заданного идентификатора. Если игрок клуба намерен оформить учетную запись с ассоциацией к электронной почте, требуется указать e-mail. Альтернативный способ – подписка по телефонному номеру.

Поскольку социальные сети повсеместно используются, разработчики интернет-казино советуют авторизоваться посредством платформы известных мессенджеров. К, Telegram, Viber, TikTok, Facebook и прочие платформы. Использовать социальные сети намного проще, в сравнении с обычные методы создания аккаунта. Новому игроку нет необходимости вводить персональные данные. Гостю игрового сайта необходимо авторизоваться в профиле соцсети и дать согласие на обработку персональных данных.

Свежий индивидуальный профиль необходимо включить. Поскольку секьюрити-отдел игрового клуба должна удостовериться, что держатель профиля и идентифицированный пользователь один и тот же человек, выполняется подтверждение данных связи. На почтовую почту участника отправляется письмо с персональным ключом. Для того чтобы верифицировать и включить аккаунт, необходимо вписать эту код в специальное место. Схожим образом выполняется включение через номера телефона. Только геймер получает не корреспонденцию, а входящее сообщение с паролем.

Активный аккаунт готов к применению всех опций игрового клуба. Посетитель официального сайта игрового клуба имеет право вносить вклады, крутить слоты за наличные и активировать бонусы. Баланс увеличивается из правильного раздела персонального аккаунта (категория «Касса»). Тут ещё доступны опции для запроса на снятие призовых.

Функционал онлайн казино без регистрации

Войти на главный сайт абсолютно возможно, даже создавая учетную запись. В отсутствие входа в собственном кабинете посетитель портала сможет:

  • проверить понравившийся онлайн-аппарат;
  • проанализировать уровень визуальных эффектов и эргономичность пользовательского интерфейса;
  • просмотреть со положениями предложений.
  • прочитать юзерское договор.

В случае если геймер зайдет на сайт не зарегистрировавшись, он увидит тот же контент страниц, что и у вошедших пользователей. На основной интернет-странице расположены такие информационные категории: список слотов, объявления соревнований и других акций, список платежных систем, блок с правилами, прочие категории меню. Чтобы визуально познакомиться с доступным содержимым, регистрировать профиль не требуется. Всякая информация азартного онлайн-проекта предоставлена в свободном доступе.

В демонстрационном варианте есть возможность проверить уровень визуального дизайна портала. Занимательнее заходить на платформы с премиальной визуализацией и современной иллюстрацией. Каждый элементы страничек официального сайта казино онлайн визуализированы четко. Информационное контент отображается в отсутствии искажений на экранах любых девайсов.

Без официального входа на аккаунт возможно поиграть в слоты. С целью этого разработан демо-режим игровых автоматов. В режиме демо в сети запускаются все устройства барабанного типа. Игровые и карточные симуляторы виртуального формата тоже обеспечивают загрузку демо-версий.

Сыграть безвозмездно в автомате можно неограниченное количество раз. В период каждой инициализации демоверсии на баланс слота добавляются очки. Например, одна тысяча или же 10000 онлайн монет. Дармовых фишек вполне, чтобы протестировать аппарат и оценить его потенциал. Если монеты исчерпались, демоигру возможно продлить. Баланс кредитов пополняется по завершении каждой запуска демоверсии.

Преимущественно пользователей предпочитает безоплатный вариант, так как старт игровых машин в демонстрационном режиме осуществляется полностью анонимно. Игрок виртуального казино не представляет никаких личных сведений. Ему пользователя необходимо только войти в официальный сайт, выбрать приглянувшийся цифровой слот и нажать клавишу «Демо».

Продолжительность испытательных сеансов никем не лимитируется. Сыграть безвозмездно допустимо хоть несколько часиков кряду. Персонал онлайн казино время запусков демоверсий не контролируют. Так как демонстрационные игры проводятся на безвозмездные фишки, оформлять учетную запись нет надобности.

Начинающим также будет полезно ознакомиться с положениями рекламных промо. Необходимо изучить базис и приобрести данные о основных определениях промо-кампаний. Свежие информация содержатся на разделе характеристик бонусов. Множество полезной данных также размещено в подрубриках секции «FAQ».

Информационный блок «Вопросы и решения» поможет разобраться не только в аспекте бонусных предложений. «Вопросы и ответы» – современная база знаний, советуемая всем новым пользователям официального сайта. Вся данные этой самой страницы разделена на релевантные категории. На этом сайте можно самим найти разъяснения на частые темы о регистрации, идентификации профиля, ставках на деньги и получении выигрышей.

Коллекция игровых автоматов в игорном заведении

Общая ассортимент азартного заведения включает примерно 2500 многообразных слотов. Игровой софт снабжают исключительно сертифицированные поставщики. Например, Novomatic, Игрософт, Эвоплей, Аматик, NetEnt, Гамомат, Спиноменал, Endorphina и прочие разработчики игрового контента.

Лицензионные игровые автоматы вулкан гэмблинг-клуба сегментированы на разделы. Существуют секции с новинками, аварийными играми и самыми востребованными вариантами. Специальными подборками представлены столовые и карточные симуляторы. Такая упорядочивание помогает сберечь часы в процессе нахождении желаемой гэмблинга.

Для того чтобы еще значительно ускорить процедуру подбора игрового аппарата, добавлена возможность отсеивания по названию. Уникальный поисковый механизм обнаружит соответствующие опции по нескольким знакам из наименования игрового автомата. С целого наименования автоматического аппарата хватит вписать минимум 3 стартовые буквы. Подходящие результаты сразу отобразятся на мониторе.

Начинающие зачастую предпочитают традиционные модификации. Классический стиль – это виртуальные автоматы на тему фруктов. Выбор иконок таких одноруких бандитов включает преимущественно из следующих изображений:

  • фрукты, фрукты;
  • черешни, апельсины
  • ягоды, яблоки;
  • звездочки, семёрки.

Виртуальные игровые автоматы 777 идеальны для новичкам, ведь они лишены трудных опций и лишних настроек. Сыграть в сети в ретро игровые машины можно без каких-либо подготовительных обучения. Призовые сочетания образуются по стандартным правилам. Для того чтобы получить определённую вознаграждение, необходимо собрать комбинацию из некоторых одинаковых знаков. Зачастую как правило призовая комбинация должна содержать по крайней мере 2 или 3 символа.

Традиционные варианты охватывают около 30-40 процентов из всего ассортимента казино зала. Регулярные игроки игорного заведения выбирают слотам с дополнительными возможностями и улучшенными игровыми настройками. Каким образом правило, это в последнее время выпущенные слоты. Актуальные новинки зачастую наделены оригинальной системой вознаграждений и уникальными бонусными функциями. В новых версиях обычно используются механики Megaways, Drops And Wins, Expanding Wild и другие.

Участвовать в онлайн казино с акциями не только увлекательнее, но и доходнее. Бонусный тур может дать большой джекпот даже по маленькой ставке. Предельные выигрыши временами достигают ×20 000-×50 000 и более высоких показателей. Ставка участника может быть повышена в тысячи раз.

Самая распространенная фича бонуса – Free Spins. Фриспины запускаются по завершении появления специальных символов Scatter-символов или Бонусных знаков. Чтобы выиграть ряд бонусных круток, необходимо собрать определенное количество особых символов. В конкретных случаях безвозмездные спины активируются случайным образом.

Иные призовые варианты, что часто имеются в официальных слотах: re-spins, дополнительные игры и увеличенные множители выплат. Дополнительные спины зачастую активируются наряду с экстра Вайлдами. Дополнительные развлечения обладают в основном стилистический стиль.

Отдельные автоматические машины наделены опцией приобретения призов. Стоимость бонусного этапа обусловлена от её потенциала. Чем дороже фриспины, тем больше вероятность выиграть главный приз. В титулах слотов с возможностью закупки дополнений добавляется надпись «Bonus Buy». Чаще в большинстве случаев стоимость дополнительного этапа изменяется в пределах ×50-×100 по отношению к совокупной бета на вращение.

Вдобавок к катушечных азартных автоматов, в списке казино онлайн есть игры в реальном времени с дилерами. Игровые увлечения данного формата включаются в категорию «Live». Популярные столовые и карточные эмуляторы: рулетка, покер, пунто банко, двадцать одно, дайсы, лото и другие. Забавы этого вида разрешается включать только в режиме на подлинные деньги.

Слоты на деньги доступны лишь зарегистрированным игрокам. Начинающий сможет играть за деньги в сети по завершении регистрации аккаунта. Новому участнику клуба также необходимо внести сумму. Порог минимального пополнения определяется в соответствии с особенностей платежного сервиса.

Премиальная система

Вошедшим на основном портале игорного заведения пользователям предлагается гибкая система вознаграждений. Бонусы предоставляются сразу по завершении создания учетной записи. Недавние посетители смогут запустить Welcome Bonus. Это вступительный подарок, для заполучить его, требуется создать аккаунт и внести начальные депозит. Первоначальный презент состоит из дополнительных средств и фриспинов. Объем премии обусловлен по размера первого пополнения.

Частые игроки в возможности самостоятельно увеличить призовой перспективы. С целью регулярнее получать акционные предложения, необходимо постоянно играть на деньги. Чем больше депозитов положил пользователь в течение конкретный период, тем богаче призы доступны ему.

Постоянные игроки на деньги онлайн приобретают депозитные призы и особенные призы в рамках программы лояльности игорного заведения. Участие пользователя также стимулируется участием в соревнования. Турнирные состязания начинаются примерно два-четыре раза ежемесячно.

Участие пользователя отражается на его быстроте подъема по уровням статусов программы верности. Рейтинг гемблера поднимается в зависимости от регулярности депозитов. Посмотреть свой настоящий статус можно в соответствующем части аккаунта.

Новичкам по дефолту присваивается начальный статус в интернет-клубе. Он станет закреплен за игроком мгновенно по завершении регистрации учетной записи. Последующая акция повлечет к пошаговому росту ранга. Повысить уровень возможно, внеся подряд ряд депозитов. Баллы программы преданности также начисляются за завершение верификации и завершенное оформление аккаунта.

Максимально действенный способ прокачать свой учетную запись – систематическая гейминг на реальные деньги. Играть в интернете доступно в любые игровые машины и табличные имитаторы. Статус увеличивается на основе количества бетсов, сделанных на реальные деньги.

Число имеющихся поощрений возрастает с перемещаясь на новую этап программы лояльности. Продвигаясь по лестнице статусов, геймер имеет возможность участвовать в очень прибыльных предложениях. Игрокам предоставляются:

  • личные премии;
  • наградные премиум-соревнования;
  • увеличивающиеся выигрыши
  • денежные лотереи
  • фриспины в выгодных слотах;
  • раскрутки Фортуны Удачи;
  • промокоды и иные подарки.

Турниры для игроков с VIP-статусом проводятся по общепринятым правилам. Их ключевое различие – объем выигрышного резерва. В линии элитных соревнований стоят рекордные выигрыши. Объем выигрышного резерва может быть сопоставим джекпоту игорного заведения.

Активным игрокам часто раздают бонус-коды. Акционный купон – это поощрение личного типа. Любой бонусный код можно применить в онлайн-казино всего один раз. Включение персонального шифра дает возможность получить премии на более выгодных требованиях.

Служба пользовательской сервиса казино вулкан казино

Служба поддержки действует в виде интернета. Сменные агенты сервиса круглосуточно пребывают на линии. Обрести профессиональную поддержку смогут как начинающие без аккаунта, так и регулярные посетители казино.

Во благо удобства посетителей интернет-ресурса предоставлено ряд каналов связи с представителями технической поддержки. Связаться с менеджером департамента возможно:

  1. По смартфона.
  2. Посредством соц платформы.
  3. С помощью цифровой почты.
  4. В групповом чате.

В данном следует иметь в виду, то, что не каждый методы общения гарантируют общение круглосуточно. Например, номер горячей линии работает только в рабочие дни (с восьми утра до пяти вечера). Приблизительно в подобном же формате действуют эксперты, которые информируют пользователей по электронной почте.

Когда вопрос требует быстрого реакции, советуется использовать лайв-чат casino. Агенты чата всегда на линии. Для того чтобы уменьшить до минимума время ожидания ответа, в одной рабочем периоде трудится сразу несколько профессионалов. Советы предоставляются обычно в течение минуты начиная с получения вопроса.

Пользователей интернет-казино интересует различная категория азартных предложений. Гости официального портала часто интересуются о выгодных слот-машинах и темпе выдачи выигрышей. К популярным вопросам тоже относится вопрос отыгрыша призовых средств. Представители сектора помощи способны реагировать на всякий обращение пользователя.

Когда официальный сайт игорного заведения становится недоступным, посетители ресурса задаются вопросом о причины блокировки. Сотрудники поддержки в этих случаях предлагают использовать актуальные копии сайтов казино. Линки на повторяющиеся странички выдаются даром. URL-адреса действующих альтернативных сайтов можно получить непосредственно в службе поддержки.

Для посетителей игорного казино важны все услуги на сайте. Сертифицированный ресурс необходимо обеспечивать выгодные условия на любых этапах игры, от начала регистрации до выдачи призов. Соблюдение норм справедливого и безопасного азартных игр безусловно отслеживается при выдаче или продлении лицензии.

Официальный азартный сайт приглашает создать аккаунт за нескольких минут. Подтверждение ещё отнимает минимальное количество минут. По завершении регистрации аккаунта посетитель приобретает абсолютный вход ко всяким одноруким бандитам на деньги. Верифицированный игрок имеет возможность не только крутить в слоты, но и применять привилегии. Полученные средства гарантировано переводятся. Вознаграждения перечисляются в отсутствие задержек и доплат со бока казино.

About the Author

Leave a Reply

Your email address will not be published. Required fields are marked *

You may also like these