/* 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(); }); } } Erotische Sauna - Elegante Begleiterinnen - Law Analysis with Rahul

Erotische Sauna – Elegante Begleiterinnen

Prostituierte Im Internet Entdecken: Die Hochwertige Selektion bei Orhidi.com.

Orhidi.com repräsentiert eine eine Klasse für sich im Bereich der Sphäre der Freizeitbekanntschaften und unterscheidet sich signifikant von der Konkurrenz fort. Die Organisation stellt zur Verfügung ein breites Sortiment exklusiver Persönlichkeiten, das sorgsam geprüft wird , um auf die unterschiedlichsten Anforderungen und Belange zu befriedigen. Was Orhidi.com besonders kennzeichnet, ist der einzigartige Vorgehensweise zur privaten Betreuung jedweden Kunden. In diesem Zusammenhang wird ein eigens auf Sie zugeschnittener Dienstleistung zur Verfügung gestellt, der entfernt von gängigen Richtlinien besteht.

Die Verpflichtung auf Hochwertigkeit bei Orhidi.com bleibt unerreicht. Jedes Exemplar geht durch einen rigorosen Auswahlprozess, welcher sicherstellt, sodass dass die größten Anforderungen der Professionalität und/oder Diskretion erfüllt sind. Aufmerksamkeit bis ins kleinste Detail wird betrachtet als in diesem Zusammenhang nicht bloß ein Anliegen, jedoch gelebte Realität. Das Ziel ist es, nicht bloß ein Geschehen, nichtsdestotrotz eine erstklassige Erfahrung zu bieten, die dauerhaft in Erinnerung bleibt.

In einem Branche, die häufig durch temporäre Kontakte charakterisiert wird, setzt Orhidi.com auf nachhaltige Beziehungen, die auf Vertrauen und wechselseitiger Hochachtung beruhen. Dank solcher unverwechselbaren Ansatz lässt sich Kunden Begleiterinnen finden, die sowohl durch äußeres Erscheinungsbild, sondern insbesondere durch ihren Charme und Reiz überzeugen. Lassen Sie sich inspirieren von der außergewöhnlichen Auswahl an Amateurbegleiterinnen anregen und entdecken Sie die Annehmlichkeiten einer Website, die für Einzigartigkeit und Qualität steht und bekannt ist. Orhidi.com wird bevorzugt von jene, die auf der Suche nach mehr als nur einer schnellen Begegnung suchen.

saunaclub in der nähe

turkish-pussyturkish-pussy ❤️ turkish-pussy, 37 Jahre, Escort aus Berlin ❤️
KIKI-VEGAKIKI-VEGA ❤️ kiki-vega, 33 Jahre, Escort aus Bochum ❤️
Luxus Tantra Wellness Erotische MassagenLuxus-Tantra-PB ❤️ Tantra / Massage-Studio Luxus Tantra Wellness Erotische Massagen in Paderborn ❤️
Kada Love Kaydaloove ❤️ Kada Love , 44 Jahre, Escort aus Nürnberg ❤️
Deutsch TürkinLuccia ❤️ Deutsch Türkin, 24 Jahre, Escort aus Heilbronn ❤️
Milla_DT ❤️ Milla_DT, 42 Jahre, Escort aus Berlin ❤️
heisse_sallyheisse_sally ❤️ heisse_sally, 63 Jahre, Escort aus Riedlingen ❤️
2biFrauen2biFrauen ❤️ 2biFrauen, 50 Jahre, Escort aus Herne ❤️
Merrylou ❤️ Merrylou, 41 Jahre, Escort aus Neunkirchen ❤️
Nadine-inkedNadine-inked ❤️ nadine-inked, 34 Jahre, Escort aus Frankfurt am Main ❤️
Alexia SantosTsAlexiaSantos ❤️ Alexia Santos, 38 Jahre, Escort aus Düsseldorf ❤️
NoSugar ❤️ NoSugar, 30 Jahre, Escort aus Düsseldorf ❤️
Lady Naomi Rouge ❤️ Lady Naomi Rouge, 31 Jahre, Escort aus Nürnberg ❤️
jessy jonesJessylove4you ❤️ jessy jones, 29 Jahre, Escort aus Stuttgart ❤️
Ts camila latinaCamilalatina ❤️ Ts camila latina, 38 Jahre, Escort aus Hamburg ❤️

Vornehme Nutten dt.weit: Entdecke Dein Modell bei Orhidi.com.

Der Vorgang der Buchung bei Orhidi.com ist unkompliziert und übersichtlich gestaltet, um sicherzustellen, dass alle Prozess von der Profilansicht bis zur Bezahlung klar und geschützt ist. Zunächst können Interessenten durch eine breite Palette an erotischen Angeboten stöbern, um die perfekte Begleitung für ihre individuellen Bedürfnisse zu finden. Jedes Nutzerprofil enthält umfangreiche Angaben und Bilder, die es ermöglichen, eine informierte Wahl zu treffen. Von attraktiven Gesellschaft bis hin zu erregenden Sinneslust Erfahrungen—hier finden Anwender garantiert die richtige Begleitung.

Sobald das geeignete Modell gewählt ist, bietet Orhidi.com eine benutzerfreundliche Reservierungsmöglichkeit, die eine nahtlose Vorbereitung ermöglicht. Klare Informationen zu Verfügbarkeit und Tarifen sind sofort verfügbar, damit der volle Prozess mühelos von vom eigenen Heim verwaltet werden kann. Fortschrittliche Sicherheitsmaßnahmen stellen sicher, dass alle privaten Informationen gesichert sind.

Für den abschließenden Vorgang bietet Orhidi.com zahlreiche vertrauenswürdiger Online-Zahlungsmethoden. Unabhängig davon, ob Kreditkarte, PayPal oder Kryptowährungen—alle Wege gewährleisten Anonymität und Schutz. Nach der Finalisierung der Buchung erhalten Nutzer eine unauffällige Mitteilung, die jegliche bedeutsamen Details beinhaltet.

Dies hier sorgt nicht bloß für eine angenehme Erfahrung, sondern auch für das komplette Vertrauen in Orhidi.com als Plattform, die großen Wert auf Schutz und Kundenzufriedenheit legt. Mit diesem offenen und nutzerfreundlichen Ansatz ist es leichter denn je, das geeignete Modell für ein unvergessliches Erotik Sex-Erfahrung zu buchen. Orhidi.com—die exklusive Wahl, die in der Bundesrepublik ihresgleichen sucht.

Eskortdamen Reservieren Leicht Gemacht: Ergründen Sie Orhidi.com.

Prostituierte buchen ganz einfach: Entdecken Sie Orhidi.com, die Seite, die sich durch ihren sorgfältigen Wahl und Überprüfungsprozess für Begleitdienste hervorhebt. Bei Orhidi.com wird größter Wert auf Qualität und Exklusivität gelegt. Das Team hinter der Webseite gewährleistet, dass nur die erstklassigsten Profile den Nutzern präsentiert werden. Jede eventuelle Escort-Lady durchläuft einen gründlichen Überprüfungsprozess, der gewährleistet, dass die Profile echt und verlässlich sind.

Für eine nutte finden wollen, stellt Orhidi.com eine variantenreiche umfangreiche Auswahl mit Rubriken, die allen individuellen Wunsch und jede Präferenzerfüllen. Exklusive VIP-Begleitungen, welche mit bemerkenswerte Eleganz und Diskretion beeindrucken, gehören ebenso zum Repertoire wie Damen mit speziellen Fähigkeiten und Sachverstand. Diese Kategorien ermöglichen es den Klienten, ebendie Art von Betreuung zu auszuwählen und zu engagieren, welche den bestimmten exakten Wünschen geeignet ist.

Darüber hinaus wird jede einzelne Escort-Dame bei Orhidi.com nach strengen Standards ausgewählt, wobei auf Aussehen, Kommunikation und Professionalität geachtet wird. Diese akkurate Auswahl sichert eine hervorragende Erfahrung und absolute Zufriedenheit. Kunden erhalten die Chance, durch eine nutzerfreundliche Webseite gezielt nach ihrem bevorzugten Modell Ausschau zu halten und die Buchung zügig und diskret zu erledigen.

Orhidi.com fungiert als vertrauensvoller Vermittler zwischen Klienten und Escorts, um sicherzustellen, dass jede Zusammenkunft den maximalen Standards an Beschaffenheit und Zuverlässigkeit entspricht. Erkunden Sie die Welt exklusiver Begleitungen mit Orhidi.com und erfreuen Sie sich an die breite Palette an premium Modellen. Suchende können ohne Mühe die richtige Escort finden und anfordern.

Amateurfreuden Entdecken: Erlesene Selektion bei Orhidi.com.

Bei Orhidi.com steht die Breite der Services im Mittelpunkt, wodurch benutzerfreundliche Angebote erreicht werden. Die Unterscheidung zwischen Incall- und Outcall-Optionen bietet die Möglichkeit, welche auf unterschiedliche Vorlieben der Klienten gezielt abzustimmen. Wenn die dien Leistungen Incall den Termin unmittelbar bei den privaten hobbyhuren ermöglichen, bieten Outcall-Dienste die Gelegenheit, eine unauffällige und komfortable Begegnung inner von der Nähe des Gasts, etwa einem Hotelzimmer oder einer Privatadresse, zu organisieren. Dieses erleichtert es den Kunden, die Lokalität zu auswählen, in der der Person sich am komfortabelsten fühlen.

Besonders in der Szene der deutschen Hobbyhuren ist es von großer Bedeutung, dass einzigartige Anforderungen und Anliegen berücksichtigt werden. Die Bandbreite an besonderen Optionen bei Orhidi.com garantiert, dass Besucher sowohl im Rahmen von Incall als auch Außer-Haus-Diensten genau das passende Erlebnis finden können. Die vielseitigen Optionen gewährleisten, dass jede Begegnung außergewöhnlich und für den einzelnen Nutzer angepasst ist.

Die von der Website stellt zur Verfügung eine exklusive Auswahl an Modellen, die auf Orhidi.com zur Verfügung stehen, was ermöglicht, den perfekten Partner zu entdecken. Ganz gleich, ob Kunden ein intimes Treffen wünschen oder auf der Suche nach einem amüsanten Gegenüber für gesellschaftliche Veranstaltungen benötigen, die Bandbreite bei Orhidi.com garantiert, dass sämtliche Wünsche erfüllt werden. Die Option, eine breite Reihe von Services zu wählen gestaltet es für Kunden einfach, das gewünschte Erlebnis zu erleben – in einer Umgebung, die Sicherheit und Diskretion gewährleistet.

Intim Erfahrungen Erleben: Hochwertige Begleiter auf Orhidi.com.

Die bekannte Internetseite Orhidi.com besitzt einen einzigartigen Ruf innerhalb der Begleitbranche, aufgrund einer jahrelangen Erfahrung erfüllter Kunden bundesweit. Der Grund, warum Orhidi.com auszeichnet, sind die erlesenen Girlfriend Experience-Dienstleistungen, die ein unverhofftes unvergessliches persönliches Erlebnis garantieren. Diese erstklassigen Begleitungen sind für ihre Verführungskunst anerkannt, als auch für ihre besonderen Rezensionen, die sie ständig von ihren Kunden erhalten. Die von Orhidi.com präsentierten Modelle stehen für höchste Klasse und Charme, was sie zur bevorzugten Wahl macht für alle, die auf der Suche nach diskreten Treffen sind oder nach einem speziellen Rendezvous suchen.

Ein zusätzlicher Punkt, der Orhidi.com hervorhebt, ist das gut aufgebaute Kundenbindungsprogramm. Dessen System belohnt treue Klienten mit einzigartigen Privilegien und Angeboten, was sowohl die Zufriedenheit und auch die Treue der Kunden deutlich erhöht. Wiederholungskäufer können somit besondere Vorteile genießen, die allein über Orhidi.com bezogen werden können.

Die Expertise der Agentur sowie der direkte Schwerpunkt auf den Zufriedenheit der Kunden garantiert, dass jede Begegnung mit einem Modell von Orhidi.com ein Erfahrung von ausgezeichneter Güte darstellt. Durch der Geheimhaltung und auch Professionalität erfüllt die Plattform die Anforderungen auch der anspruchsvollsten Klientel. Bauen Sie auf die Orhidi.com, um kommendes privaten Zeitvertreib ausgiebig zu genießen und eine von den einmaligen Begegnungen zu erleben, die die Branche zu offerieren hat.

About the Author

Leave a Reply

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

You may also like these