PluginProbe
Page Builder: Pagelayer – Drag and Drop website builder / 2.1.9
Page Builder: Pagelayer – Drag and Drop website builder v2.1.9
2.2.1 2.2.0 2.1.9 2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 trunk 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 1.0.0 1.0.2 1.0.3 1.0.4 1.0.5 All 129 releases
pagelayer / js / pagelayer-pen.js

pagelayer-pen.js in Page Builder: Pagelayer – Drag and Drop website builder 2.1.9, at js/pagelayer-pen.js

1,667 lines 43.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* Pagelayer Pen editor */
2 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"];
3
4 var pagelayer_pen_sizeList = ['normal', 'x-small', 'small', 'medium', 'large', 'x-large'];
5 var pagelayer_pen_lineHeight = ['0.9', '1', '1.5', '2.0', '2.5','3.0', '3.5', '4.0', '4.5', '5.0'];
6
7 class PagelayerPen{
8
9 constructor(jEle, options) {
10 var t = this;
11
12 t.editor = jQuery(jEle);
13 t.options = options;
14
15 // Get the document of the element. It is used to make the plugin
16 // compatible with iframes.
17 t.doc = jEle.ownerDocument || document;
18 t.tagToButton = {};
19 t.optionsCounter = 0;
20 t.destroyEd = true;
21 t.semantic = null;
22 t.DEFAULT_SEMANTIC_MAP = {
23 'b': 'strong',
24 'i': 'em',
25 's': 'strike',
26 //'strike': 'del',
27 'div': 'p'
28 };
29
30 // Init editor
31 t.addHandlers();
32 t.init();
33
34 }
35
36 init(){
37 var t = this;
38 // Init Editor
39 t.editor.addClass('pagelayer-pen');
40 t.penHolder = t.addContainer();
41 t.addEvents();
42 }
43
44 addHandlers(){
45 // TODO : Add for custom plugins
46 // TODO remove all execCommands
47 this.handlers = {
48 bold:{
49 tag: 'STRONG',
50 icon: '<strong><i class="fas fa-bold"></i></strong>'
51 },
52 italic:{
53 tag: 'EM',
54 icon: '<strong><i class="fas fa-italic"></i></strong>'
55 },
56 underline:{
57 tag: 'U',
58 icon: '<strong><i class="fas fa-underline"></i></strong>'
59 },
60 strike:{
61 tag: 'strike',
62 fn: 'strikethrough',
63 icon: '<strong><i class="fas fa-strikethrough"></i></strong>'
64 },
65 h1:{
66 fn: 'formatBlock',
67 icon: '<strong>H<sub>1</sub></strong>'
68 },
69 h2:{
70 fn: 'formatBlock',
71 icon: '<strong>H<sub>2</sub></strong>'
72 },
73 h3:{
74 fn: 'formatBlock',
75 icon: '<strong>H<sub>3</sub></strong>'
76 },
77 h4:{
78 fn: 'formatBlock',
79 icon: '<strong>H<sub>4</sub></strong>'
80 },
81 h5:{
82 fn: 'formatBlock',
83 icon: '<strong>H<sub>5</sub></strong>'
84 },
85 h6:{
86 fn: 'formatBlock',
87 icon: '<strong>H<sub>6</sub></strong>'
88 },
89 p:{
90 fn: 'formatBlock',
91 icon: '<strong><i class="fas fa-paragraph"></i></strong>'
92 },
93 blockquote:{
94 fn: 'formatBlock',
95 icon: '<strong><i class="fas fa-quote-right"></i></strong>'
96 },
97 formating:{
98 fn: 'formatBlock',
99 fixIcon: '<strong><i class="fas fa-paragraph"></i></strong>'
100 },
101 unorderedlist:{
102 tag: 'UL',
103 fn: 'insertUnorderedList',
104 icon: '<strong><i class="fas fa-list-ul"></i></i></strong>'
105 },
106 orderedlist:{
107 tag: 'OL',
108 fn: 'insertOrderedList',
109 icon: '<strong><i class="fas fa-list-ol"></i></i></strong>'
110 },
111 sub:{
112 tag: 'sub',
113 fn: 'subscript',
114 icon: '<strong><i class="fas fa-subscript"></i></strong>'
115 },
116 super:{
117 tag: 'sup',
118 fn: 'superscript',
119 icon: '<strong><i class="fas fa-superscript"></i></strong>'
120 },
121 link:{
122 fn: 'setLinkHandler',
123 tag: 'a',
124 icon: '<strong><i class="fas fa-link"></i></strong>',
125 },
126 image:{
127 fn: 'imageBtnHandler',
128 icon: '<i class="far fa-image"></i>'
129 },
130 align:{
131 style: 'text-align',
132 fn: 'formatBlock',
133 icon: {
134 'left': '<i class="fas fa-align-left"></i>',
135 'center': '<i class="fas fa-align-center"></i>',
136 'right': '<i class="fas fa-align-right"></i>',
137 'justify': '<i class="fas fa-align-justify"></i>',
138 }
139 },
140 color:{
141 class: 'pagelayer-pen-color-picker',
142 style: 'color',
143 fn: 'commandHandler',
144 fixIcon: '<svg viewbox=\"0 0 18 18\"> <line class=\"pagelayer-pen-color-label pagelayer-pen-stroke pagelayer-pen-transparent\" x1=3 x2=15 y1=15 y2=15></line> <polyline class=pagelayer-pen-stroke points=\"5.5 11 9 3 12.5 11\"></polyline> <line class=pagelayer-pen-stroke x1=11.63 x2=6.38 y1=9 y2=9></line> </svg>',
145 buildBtn : 'buildColorBtnHandler',
146 default : pagelayer_customColor,
147 customInpute: true
148 },
149 background:{
150 class: 'pagelayer-pen-color-picker',
151 style: 'background-color',
152 fn: 'commandHandler',
153 fixIcon: '<svg viewbox=\"0 0 18 18\"> <g class=\"pagelayer-pen-fill pagelayer-pen-color-label\"> <polygon points=\"6 6.868 6 6 5 6 5 7 5.942 7 6 6.868\"></polygon> <rect height=1 width=1 x=4 y=4></rect> <polygon points=\"6.817 5 6 5 6 6 6.38 6 6.817 5\"></polygon> <rect height=1 width=1 x=2 y=6></rect> <rect height=1 width=1 x=3 y=5></rect> <rect height=1 width=1 x=4 y=7></rect> <polygon points=\"4 11.439 4 11 3 11 3 12 3.755 12 4 11.439\"></polygon> <rect height=1 width=1 x=2 y=12></rect> <rect height=1 width=1 x=2 y=9></rect> <rect height=1 width=1 x=2 y=15></rect> <polygon points=\"4.63 10 4 10 4 11 4.192 11 4.63 10\"></polygon> <rect height=1 width=1 x=3 y=8></rect> <path d=M10.832,4.2L11,4.582V4H10.708A1.948,1.948,0,0,1,10.832,4.2Z></path> <path d=M7,4.582L7.168,4.2A1.929,1.929,0,0,1,7.292,4H7V4.582Z></path> <path d=M8,13H7.683l-0.351.8a1.933,1.933,0,0,1-.124.2H8V13Z></path> <rect height=1 width=1 x=12 y=2></rect> <rect height=1 width=1 x=11 y=3></rect> <path d=M9,3H8V3.282A1.985,1.985,0,0,1,9,3Z></path> <rect height=1 width=1 x=2 y=3></rect> <rect height=1 width=1 x=6 y=2></rect> <rect height=1 width=1 x=3 y=2></rect> <rect height=1 width=1 x=5 y=3></rect> <rect height=1 width=1 x=9 y=2></rect> <rect height=1 width=1 x=15 y=14></rect> <polygon points=\"13.447 10.174 13.469 10.225 13.472 10.232 13.808 11 14 11 14 10 13.37 10 13.447 10.174\"></polygon> <rect height=1 width=1 x=13 y=7></rect> <rect height=1 width=1 x=15 y=5></rect> <rect height=1 width=1 x=14 y=6></rect> <rect height=1 width=1 x=15 y=8></rect> <rect height=1 width=1 x=14 y=9></rect> <path d=M3.775,14H3v1H4V14.314A1.97,1.97,0,0,1,3.775,14Z></path> <rect height=1 width=1 x=14 y=3></rect> <polygon points=\"12 6.868 12 6 11.62 6 12 6.868\"></polygon> <rect height=1 width=1 x=15 y=2></rect> <rect height=1 width=1 x=12 y=5></rect> <rect height=1 width=1 x=13 y=4></rect> <polygon points=\"12.933 9 13 9 13 8 12.495 8 12.933 9\"></polygon> <rect height=1 width=1 x=9 y=14></rect> <rect height=1 width=1 x=8 y=15></rect> <path d=M6,14.926V15H7V14.316A1.993,1.993,0,0,1,6,14.926Z></path> <rect height=1 width=1 x=5 y=15></rect> <path d=M10.668,13.8L10.317,13H10v1h0.792A1.947,1.947,0,0,1,10.668,13.8Z></path> <rect height=1 width=1 x=11 y=15></rect> <path d=M14.332,12.2a1.99,1.99,0,0,1,.166.8H15V12H14.245Z></path> <rect height=1 width=1 x=14 y=15></rect> <rect height=1 width=1 x=15 y=11></rect> </g> <polyline class=pagelayer-pen-stroke points=\"5.5 13 9 5 12.5 13\"></polyline> <line class=pagelayer-pen-stroke x1=11.63 x2=6.38 y1=11 y2=11></line> </svg>',
154 buildBtn: 'buildColorBtnHandler',
155 default : pagelayer_customColor,
156 customInpute: true
157 },
158 size:{
159 class: 'pagelayer-pen-size-picker',
160 style: 'font-size',
161 fn: 'commandHandler',
162 default : pagelayer_pen_sizeList,
163 customInpute: true
164 },
165 lineheight:{
166 style: 'line-height',
167 fn: 'commandHandler',
168 fixIcon: '<svg viewBox="0 0 22 18" version="1.1"><g><path class="pagelayer-pen-fill" d="M 21.527344 7.875 L 9.269531 7.875 C 9.011719 7.875 8.800781 8.125 8.800781 8.4375 L 8.800781 9.5625 C 8.800781 9.875 9.011719 10.125 9.269531 10.125 L 21.527344 10.125 C 21.789062 10.125 22 9.875 22 9.5625 L 22 8.4375 C 22 8.125 21.789062 7.875 21.527344 7.875 Z M 21.527344 13.5 L 9.269531 13.5 C 9.011719 13.5 8.800781 13.75 8.800781 14.0625 L 8.800781 15.1875 C 8.800781 15.5 9.011719 15.75 9.269531 15.75 L 21.527344 15.75 C 21.789062 15.75 22 15.5 22 15.1875 L 22 14.0625 C 22 13.75 21.789062 13.5 21.527344 13.5 Z M 21.527344 2.25 L 9.269531 2.25 C 9.011719 2.25 8.800781 2.5 8.800781 2.8125 L 8.800781 3.9375 C 8.800781 4.25 9.011719 4.5 9.269531 4.5 L 21.527344 4.5 C 21.789062 4.5 22 4.25 22 3.9375 L 22 2.8125 C 22 2.5 21.789062 2.25 21.527344 2.25 Z M 6.050781 5.0625 C 6.542969 5.0625 6.785156 4.453125 6.4375 4.101562 L 3.6875 1.289062 C 3.472656 1.070312 3.125 1.070312 2.910156 1.289062 L 0.160156 4.101562 C -0.160156 4.429688 0.0117188 5.0625 0.550781 5.0625 L 2.199219 5.0625 L 2.199219 12.9375 L 0.550781 12.9375 C 0.0585938 12.9375 -0.183594 13.546875 0.160156 13.898438 L 2.910156 16.710938 C 3.125 16.929688 3.476562 16.929688 3.691406 16.710938 L 6.441406 13.898438 C 6.757812 13.570312 6.585938 12.9375 6.050781 12.9375 L 4.398438 12.9375 L 4.398438 5.0625 Z M 6.050781 5.0625 "/></g></svg>',
169 default : pagelayer_pen_lineHeight,
170 customInpute: true
171 },
172 font:{
173 style: 'font-family',
174 fn: 'commandHandler',
175 fixIcon: '<i class="fas fa-font"></i>',
176 default : pagelayer_fonts,
177 buildBtn : 'buildfontBtnHandler',
178 },
179 viewHTML:{
180 fn: 'viewHTMLBtnHandler',
181 icon: '<i class="fas fa-code"></i>'
182 },
183 removeformat:{
184 icon: '<i class="fas fa-remove-format"></i>'
185 }
186 }
187 }
188
189 addContainer(className){
190
191 className = className || false;
192
193 // Add Container
194 var container = jQuery('.pagelayer-pen-holder');
195
196 if(container.length < 1){
197 jQuery('body').append('<div class="pagelayer-pen-holder"></div>');
198 container = jQuery('.pagelayer-pen-holder');
199 }
200
201 if(!className){
202 return container;
203 }
204
205 if(container.find('.'+className).length < 1){
206 container.append('<div class="'+className+'"></div>');
207 }
208
209 return container.find('.'+className);
210
211 }
212
213 addToolbar(){
214
215 // Add Toolbar
216 var t = this;
217 var groups = t.options.toolbar;
218 var toolbar = t.toolbar = t.addContainer('pagelayer-pen-toolbar');
219
220 // Make it empty
221 toolbar.empty();
222
223 if (!Array.isArray(groups[0])) {
224 groups = [groups];
225 }
226
227 var addButton = function(container, format, value){
228
229 var btn = t.handlers[format];
230 var icon = '';
231
232 if('icon' in btn){
233 var _icon = btn['icon'];
234
235 if(typeof _icon == 'object' && !pagelayer_empty(_icon[value])){
236 icon = _icon[value];
237 }else if(typeof icon == 'string'){
238 icon = _icon;
239 }
240 }
241
242 var input = document.createElement('button');
243 input.setAttribute('type', 'button');
244 input.setAttribute('data-format', format);
245 input.classList.add('pagelayer-pen-' + format);
246
247 if('class' in btn){
248 input.classList.add(btn['class']);
249 }
250
251 if( pagelayer_empty(value) && 'default' in btn ){
252 value = btn['default'];
253 }
254
255 input.innerHTML = icon;
256 if(value != null) {
257 input.value = value;
258 }
259 container.appendChild(input);
260 }
261
262 var createoption = function(val, lang, type){
263 type = type || '';
264 var lang = pagelayer_empty(lang) ? 'Default' : lang;
265 return '<option value="'+val+'" type="'+type+'">'+lang+'</option>';
266 }
267
268 var addSelect = function(container, format, values) {
269
270 var input = document.createElement('select');
271 input.classList.add('pagelayer-pen-' + format);
272
273 if('class' in t.handlers[format]){
274 input.classList.add(t.handlers[format]['class']);
275 }
276
277 input.setAttribute('data-format', format);
278
279 if( pagelayer_empty(values) && 'default' in t.handlers[format] ){
280 values = t.handlers[format]['default'];
281 }
282
283 for(var kk in values){
284 var options = '';
285 var value = values[kk];
286
287 if(typeof value == 'object') {
288 if(kk != 'default'){
289 options += '<optgroup label="'+pagelayer_ucwords(kk)+'">';
290 }
291 for(y in value){
292 options += createoption((jQuery.isNumeric(y) ? value[y] : x), value[y], kk);
293 }
294 }else if(value !== false) {
295 options += createoption(value, value);
296 } else {
297 options += createoption('', '');
298 }
299
300 jQuery(input).append(options);
301 }
302
303 container.appendChild(input);
304 }
305
306 groups.forEach(function(controls){
307 var group = document.createElement('span');
308 group.classList.add('pagelayer-pen-formats');
309
310 controls.forEach(function (control){
311 var format = control;
312
313 if(typeof control === 'object'){
314 format = Object.keys(control)[0];
315 }
316
317 if( pagelayer_empty(t.handlers[format]) ){
318 return;
319 }
320
321 if( typeof control === 'string' ){
322 addButton(group, control);
323 } else {
324 var value = control[format];
325 if (Array.isArray(value)) {
326 addSelect(group, format, value);
327 } else {
328 addButton(group, format, value);
329 }
330 }
331
332 var btn = t.handlers[format];
333 t.tagToButton[(btn.tag || btn.style || format).toLowerCase()] = format;
334 });
335
336 // TODO skip if format is not exist
337 toolbar[0].appendChild(group);
338 });
339
340 toolbar.find('button').on('click', function(){
341 var bEle = jQuery(this);
342 var format = bEle.data('format');
343
344 if(! format in t.handlers){
345 return;
346 }
347
348 var btn = t.handlers[format];
349 t.currentFormat = format;
350 t.execCmd(btn.fn || format, btn.param || format, btn.forceCss);
351 });
352
353 toolbar.find('select').on('change', function(e){
354 var bEle = jQuery(this);
355 var format = bEle.data('format');
356 var val = bEle.val();
357
358 if(! format in t.handlers){
359 return;
360 }
361
362 var btn = t.handlers[format];
363 t.currentFormat = format;
364 t.execCmd(btn.fn || format, val, btn.forceCss);
365 });
366
367 toolbar.find('select').each(function(){
368 var format = jQuery(this).data('format');
369
370 if('buildBtn' in t.handlers[format]){
371
372 try{
373 t[t.handlers[format]['buildBtn']](this);
374 }catch(e){
375 try{
376 t.handlers[format]['buildBtn'](this);
377 }catch(e2){
378 t.buildDropdown(this);
379 }
380 }
381
382 return true;
383 }
384
385 t.buildDropdown(this);
386 });
387
388 // Add close button
389 toolbar.append('<span class="pagelayer-pen-formats"><button class="pagelayer-pen-close"><i class="fas fa-times"></i></button></span>');
390
391 // Hide editor on click close tool handler
392 toolbar.find('.pagelayer-pen-close').on('mousedown', function(e){
393 //e.preventDefault();
394 t.destroyEd = true;
395 t.editor.trigger('blur');
396 });
397
398 }
399
400 execCmd(cmd, param, forceCss, skipPen){
401 var t = this;
402 skipPen = !!skipPen || '';
403
404 if(cmd !== 'dropdown'){
405 t.focus();
406 t.restoreRange();
407 }
408
409 try{
410 document.execCommand('styleWithCSS', false, forceCss || false);
411 }catch(c){}
412
413 try{
414 t[cmd + skipPen](param);
415 }catch(c){
416 try{
417 cmd(param);
418 }catch(e2){
419 if(cmd === 'insertHorizontalRule'){
420 param = undefined;
421 }else if (cmd === 'formatBlock'){ // TODO: check for && t.isIE
422 param = '<' + param + '>';
423 }
424
425 document.execCommand(cmd, false, param);
426 t.semanticCode();
427 t.restoreRange();
428 }
429 }
430
431 if(cmd !== 'dropdown'){
432 t.updateButtonStatus();
433 t.editor.trigger('input');
434 }
435
436 }
437
438 commandHandler(value){
439 var t = this;
440 var format = t.currentFormat;
441
442 if( pagelayer_empty(format) ){
443 return;
444 }
445
446 var btn = t.handlers[format];
447 var sel = window.getSelection();
448 var text = t.range.commonAncestorContainer;
449 var selectedText = t.range.cloneContents();
450 selectedText = jQuery('<div>').append(selectedText).html();
451
452 // Also select the tag
453 if(text.nodeType === Node.TEXT_NODE){
454 text = text.parentNode;
455 }
456
457 if (text.innerHTML === selectedText && text != t.editor[0]) {
458 var ele = jQuery(text);
459 if('tag' in btn){
460 // Replace tag
461 }else if('style' in btn){
462 var style = {};
463 style[btn.style] = value;
464
465 ele.css(style);
466 }else if('atts' in btn){
467 // Add attribute or toggle the element
468 }
469 } else {
470
471 // TODO for toggle tags and add tags
472 var html = jQuery('<span style="'+btn.style+':' + value + ';">' + selectedText + '</span>');
473
474 // Remove style from all childrend
475 var style = {};
476 style[btn.style] = '';
477 html.find('[style]').css(style);
478 // TODO: remove span element that have no atts
479 var node = html[0];
480 var firstInsertedNode = node.firstChild;
481 var lastInsertedNode = node.lastChild;
482 t.range.deleteContents();
483 t.range.insertNode(node);
484
485 if(firstInsertedNode) {
486 t.range.setStartBefore(firstInsertedNode);
487 t.range.setEndAfter(lastInsertedNode);
488 }
489
490 // Is previous element empty?
491 var prev = jQuery(node).prev();
492
493 if( prev.length > 0 && prev.is(':empty') ){
494 prev.remove();
495 }
496 }
497
498 sel.removeAllRanges();
499 sel.addRange(t.range);
500
501 }
502
503 formatBlock(value){
504
505 var t = this,
506 format = t.currentFormat,
507 btn = t.handlers[format],
508 startNode = t.range.startContainer,
509 endNode = t.range.endContainer;
510
511 if( startNode.nodeType == Node.TEXT_NODE && startNode.parentNode != t.editor[0] ){
512 startNode = startNode.parentNode;
513 }
514
515 if( endNode.nodeType == Node.TEXT_NODE && endNode.parentNode != t.editor[0] ){
516 endNode = endNode.parentNode;
517 }
518
519 // TODO: only for seleced content
520 // Wrap text nodes in span for easier processing
521 t.editor.contents().filter(function () {
522 return this.nodeType === 3 && this.nodeValue.trim().length > 0;
523 }).wrap('<span data-pts/>');
524
525 var isLineEnd = function(lEle){
526 return lEle == null || lEle.nodeName == 'BR' || t.isline(lEle);
527 }
528
529 var wrapLine = function(pLine){
530
531 var pLine = jQuery(pLine),
532 lineFele,
533 lineEele,
534 finalP;
535
536 // Get Parent Element
537 if(pLine.parentsUntil(t.editor).length > 0){
538 pLine = pLine.parentsUntil(t.editor).last();
539 }
540
541 if(t.isline(pLine)){
542 return pLine;
543 }
544
545 // Get line first element
546 if(isLineEnd(pLine[0].previousSibling)){
547 lineFele = pLine;
548 }else{
549 lineFele = pLine.prevAll().filter(function(){
550 return isLineEnd(this.previousSibling);
551 }).first();
552 }
553
554 // Get line last element
555 if(isLineEnd(lineFele[0].nextSibling)){
556 lineEele = lineFele;
557 }else{
558 lineEele = lineFele.nextAll().filter(function(){
559 return isLineEnd(this.nextSibling);
560 }).first();
561 }
562
563 // Wrap all with p tag
564 if(lineFele.is(lineEele)){
565 finalP = lineFele.wrap('<p/>').parent()
566 }else{
567 finalP = lineFele.nextUntil(lineEele.next()).addBack().wrapAll('<p/>').parent();
568 }
569
570 finalP.next('br').remove();
571 return finalP;
572 }
573
574 // Get start block lavel elements
575 var $sNode = jQuery(t.blockNode(startNode));
576 if($sNode.is(t.editor)){
577 $sNode = wrapLine(startNode);
578 }
579
580 var $eNode = jQuery(t.blockNode(endNode));
581 if($eNode.is(t.editor)){
582 $eNode = wrapLine(endNode);
583 }
584
585 var $oldEle = $sNode;
586
587 if(! $sNode.is($eNode) ){
588
589 var findEnd = false;
590
591 var addElement = function(addEle){
592 if(addEle[0].nodeName == 'UL' || addEle[0].nodeName == 'OL') {
593 addEle.children().each(function(){
594 $oldEle = $oldEle.add(jQuery(this));
595 });
596 return;
597 }
598 $oldEle = $oldEle.add(addEle);
599 }
600
601 var wrapAllEle = function(nextEle){
602
603 if(nextEle.is($eNode) || nextEle.find($eNode).length > 0){
604 findEnd = true;
605 return;
606 }
607
608 if(nextEle.length < 1){
609 return;
610 }
611
612 if(!t.isline(nextEle[0])){
613 nextEle = wrapLine(nextEle);
614 }
615
616 addElement(nextEle);
617 wrapAllEle( nextEle.next() );
618 }
619
620 wrapAllEle($sNode.next());
621
622 // Is start Element have a another parent
623 var pars = $sNode.parentsUntil(t.editor);
624
625 pars.each(function(){
626 var $par = jQuery(this);
627 wrapAllEle($par.next());
628 });
629
630 if( pars.length > 0 ){
631 $sNode = pars.last();
632 }
633
634 var nextEnd = $sNode.nextAll().filter(function(){
635 return jQuery(this).is($eNode) || jQuery(this).find($eNode).length > 0;
636 }).first();
637
638 // Add elements
639 if( nextEnd.length > 0 ){
640 var $nextEle = $sNode.nextUntil(nextEnd);
641 $nextEle.each(function(){
642 var ulEle = jQuery(this);
643 if($oldEle.has(ulEle)) return;
644 addElement(ulEle);
645 });
646 }
647
648 // Add end element
649 if(nextEnd.length > 0 && !nextEnd.is($eNode) && (nextEnd[0].nodeName == 'UL' || nextEnd[0].nodeName == 'OL')){
650 nextEnd.children().each(function(){
651 var li = jQuery(this);
652 $oldEle = $oldEle.add(li);
653 if(li.is($eNode) || li.find($eNode).length > 0) return false;
654 });
655 }else{
656 $oldEle = $oldEle.add($eNode);
657 }
658 }
659
660 if('style' in btn){
661 var style = {};
662 style[btn.style] = value;
663
664 $oldEle.css(style);
665 }else if('atts' in btn){
666 // Add attribute or toggle the element
667 var attr = {};
668 attr[btn.atts] = value;
669
670 $oldEle.attr(attr);
671 }else{
672 // Replace tag
673 var tag = value.toLowerCase();
674
675 // need to find all block ele and replace this
676 $oldEle.each( function(){
677
678 var $cEle = jQuery(this);
679
680 if($cEle.is(t.editor)){
681 return;
682 }
683
684 // Is List element
685 if($cEle.css('display') == 'list-item'){
686 if( t.isline($cEle[0].firstChild)){
687 $cEle.children().each(function(){
688 var liChild = jQuery(this);
689
690 if(t.isline(liChild[0])){
691 t.replaceTag(liChild, tag, true);
692 return;
693 }
694 // TODO: Check and need to correct
695 liChild.wrap('<' + tag + '/>');
696 liChild.next('br').remove();
697
698 });
699 return
700 }
701
702 $cEle.contents().wrapAll('<' + tag + '/>');
703 return;
704 }
705
706 t.replaceTag($cEle, tag, true);
707 });
708 }
709
710 // Get rid of pen temporary span's
711 jQuery('[data-pts]', t.editor).contents().unwrap();
712 t.semanticCode();
713 t.restoreRange();
714 }
715
716 blockNode( node ){
717 var t = this;
718 while( !t.isline(node) && node != t.editor[0] ) {
719 node = node.parentNode;
720 }
721 return node;
722 }
723
724 isline(node){
725 if (node.nodeType !== Node.ELEMENT_NODE) return false;
726 if (node.childNodes.length === 0) return false; // Exclude embed blocks
727 var style = window.getComputedStyle(node);
728 return ['block', 'list-item'].indexOf(style.display) > -1;
729 }
730
731 replaceTag(ele, tag, copyAttr){
732 ele.wrap('<' + tag + '/>');
733
734 var par = ele.parent();
735
736 if(copyAttr){
737 jQuery.each(ele.prop('attributes'), function () {
738 par.attr(this.name, this.value);
739 });
740 }
741
742 ele.contents().unwrap();
743
744 return par;
745 }
746
747 semanticCode(){
748 var t = this;
749 t.semanticTag('b');
750 t.semanticTag('i');
751 t.semanticTag('s');
752 t.semanticTag('strike');
753 t.semanticTag('div', true);
754 }
755
756 semanticTag(oldTag, copyAttributes){
757 var t = this;
758 var newTag;
759
760 if(t.semantic != null && typeof t.semantic === 'object' && t.semantic.hasOwnProperty(oldTag)){
761 newTag = t.semantic[oldTag];
762 } else if (t.DEFAULT_SEMANTIC_MAP.hasOwnProperty(oldTag)) {
763 newTag = t.DEFAULT_SEMANTIC_MAP[oldTag];
764 } else {
765 return;
766 }
767
768 jQuery(oldTag, t.editor).each(function () {
769 var $oldTag = jQuery(this);
770 if($oldTag.contents().length === 0) {
771 return false;
772 }
773
774 t.replaceTag($oldTag, newTag, copyAttributes);
775 });
776 }
777
778 addEvents(){
779 // Add Events
780 var t = this,
781 editor = t.editor,
782 ctrl = false,
783 debounceButtonStatus,
784 debounceShowToolbar,
785 touchTimeout;
786
787 var showToolBar = function(){
788
789 var jEle = t.penHolder.children(':visible');
790
791 if(jEle.length < 1){
792 jEle = t.toolbar;
793 }
794
795 t.showPen(jEle);
796 };
797
798 // Debounced toolbar show - Performance optimization
799 var debouncedShowToolBar = function(){
800 clearTimeout(debounceShowToolbar);
801 debounceShowToolbar = setTimeout(showToolBar, 50);
802 };
803
804 // Track selection changes with debounce
805 editor.on('mouseup keyup', function(e){
806 t.saveRange();
807 });
808
809 // Save range with debounce
810 var debouncedSaveRange = function(){
811 clearTimeout(debounceButtonStatus);
812 debounceButtonStatus = setTimeout(function(){
813 t.saveRange();
814 }, 100);
815 };
816
817 editor.on('focusout', function(e){
818
819 if(t.destroyEd){
820 t.editor.removeClass('pagelayer-pen-focused');
821 t.range = null;
822 return;
823 }
824
825 t.saveRange();
826
827 });
828
829 // Prevent to hide toolbar - Fix focus issues
830 t.penHolder.on('mousedown click touchstart', function(e){
831 // Prevent focus from leaving toolbar
832 if(jQuery(e.target).closest('input').length < 1){
833 e.preventDefault();
834 e.stopPropagation();
835 }
836
837 t.destroyEd = false;
838
839 // Re-enable destroy after toolbar interaction
840 setTimeout(function(){
841 t.destroyEd = true;
842 }, 200);
843 });
844
845 // Prevent toolbar buttons from stealing focus, but allow inputs
846 t.penHolder.find('button, span').on('mousedown', function(e){
847 if (jQuery(e.target).is('input, select, textarea')) {
848 return;
849 }
850 e.preventDefault();
851 e.stopPropagation();
852 });
853
854 // On editor blur - Improved focus management
855 editor.on('blur', function(e){
856
857 // Check if focus moved to toolbar
858 var relatedTarget = e.relatedTarget;
859 if(relatedTarget && (jQuery(relatedTarget).closest(t.penHolder).length > 0 ||
860 jQuery(relatedTarget).closest('.pagelayer-pen-link-tooltip').length > 0)){
861 return;
862 }
863
864 if(!t.destroyEd){
865 return;
866 }
867
868 t.destroy();
869 });
870
871 // Keydown improvements
872 editor.on('keydown', function(e){
873 t.penHolder.hide();
874
875 // Keyboard shortcuts
876 if((e.ctrlKey || e.metaKey) && !e.altKey){
877 var key = String.fromCharCode(e.which).toLowerCase();
878
879 switch(key){
880 case 'b':
881 e.preventDefault();
882 t.execCmd('bold');
883 break;
884 case 'i':
885 e.preventDefault();
886 t.execCmd('italic');
887 break;
888 case 'u':
889 e.preventDefault();
890 t.execCmd('underline');
891 break;
892 }
893 }
894
895 // Escape key to close toolbar
896 if(e.keyCode === 27){
897 t.destroy();
898 editor.trigger('blur');
899 }
900 });
901
902 editor.on('mousedown', function(){
903 if(t.editor.attr('contenteditable') == 'true'){
904 t.showPen();
905 }
906 });
907
908 // Touch support for mobile
909 editor.on('touchstart', function(e){
910 touchTimeout = setTimeout(function(){
911 t.showPen();
912 }, 300);
913 });
914
915 editor.on('touchmove touchend', function(){
916 clearTimeout(touchTimeout);
917 });
918
919 editor.on('mouseup keyup keydown', function(e){
920 if ((!e.ctrlKey && !e.metaKey) || e.altKey) {
921 setTimeout(function () { // "hold on" to the ctrl key for 50ms
922 ctrl = false;
923 }, 50);
924 }
925
926 clearTimeout(debounceButtonStatus);
927 debounceButtonStatus = setTimeout(function () {
928 t.updateButtonStatus();
929 }, 100);
930
931 });
932
933 // Set focus on editor - Improved
934 editor.on('click', function(e){
935
936 if(t.editor.hasClass('pagelayer-pen-focused')){
937 // Just show toolbar, don't re-focus
938 t.showPen();
939 return;
940 }
941
942 t.editor.attr('contenteditable', 'true');
943 t.editor.focus();
944 });
945
946 // Set focus on editor - Improved with touch support
947 editor.on('focus', function(){
948 t.destroyEd = true;
949 t.addToolbar();
950 t.showPen();
951 t.editor.addClass('pagelayer-pen-focused');
952 jQuery(window).unbind('scroll.penToobar');
953 jQuery(window).on('scroll.penToobar', debouncedShowToolBar);
954 jQuery(document).unbind('mousemove.penToobar');
955 jQuery(document).on('mousemove.penToobar', debouncedShowToolBar);
956 });
957
958 t.semanticCode();
959 }
960
961 destroy(){
962 var t = this;
963 // Hide toolbar properly
964 t.penHolder.hide();
965
966 // Removing event listeners
967 jQuery(document).unbind('mousemove.penToobar');
968 jQuery(window).unbind('scroll.penToobar');
969 jQuery(document).unbind('click.pagelayerLinkTooltip');
970
971 // Clear any pending timeouts
972 t.penHolder.find('input').unbind('keydown');
973 }
974
975 hasFocus(){
976 var t = this;
977 return (
978 t.doc.activeElement === t.editor ||
979 t.contains( t.editor[0], t.doc.activeElement)
980 );
981 }
982
983 contains(parent, descendant) {
984 try {
985 // Firefox inserts inaccessible nodes around video elements
986 descendant.parentNode; // eslint-disable-line no-unused-expressions
987 } catch (e) {
988 return false;
989 }
990 return parent.contains(descendant);
991 }
992
993 saveRange(){
994 var t = this,
995 selection = t.doc.getSelection();
996
997 if (!selection || !selection.rangeCount || !t.hasFocus()) {
998 return;
999 }
1000
1001 var savedRange = t.range = selection.getRangeAt(0),
1002 range = t.doc.createRange(),
1003 rangeStart;
1004 range.selectNodeContents(t.editor[0]);
1005 range.setEnd(savedRange.startContainer, savedRange.startOffset);
1006 rangeStart = (range + '').length;
1007 t.metaRange = {
1008 start: rangeStart,
1009 end: rangeStart + (savedRange + '').length
1010 };
1011 }
1012
1013 restoreRange(){
1014 var t = this,
1015 metaRange = t.metaRange,
1016 savedRange = t.range,
1017 selection = t.doc.getSelection(),
1018 range;
1019
1020 if(!savedRange){
1021 return;
1022 }
1023
1024 if(metaRange && metaRange.start !== metaRange.end){ // Algorithm from http://jsfiddle.net/WeWy7/3/
1025 var charIndex = 0,
1026 nodeStack = [t.editor[0]],
1027 node,
1028 foundStart = false,
1029 stop = false;
1030
1031 range = t.doc.createRange();
1032
1033 while(!stop && (node = nodeStack.pop())){
1034 if (node.nodeType === 3){
1035 var nextCharIndex = charIndex + node.length;
1036 if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) {
1037 range.setStart(node, metaRange.start - charIndex);
1038 foundStart = true;
1039 }
1040 if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) {
1041 range.setEnd(node, metaRange.end - charIndex);
1042 stop = true;
1043 }
1044 charIndex = nextCharIndex;
1045 } else {
1046 var cn = node.childNodes,
1047 i = cn.length;
1048
1049 while (i > 0) {
1050 i -= 1;
1051 nodeStack.push(cn[i]);
1052 }
1053 }
1054 }
1055 }
1056
1057 selection.removeAllRanges();
1058 selection.addRange(range || savedRange);
1059 }
1060
1061 getRange(){
1062 var t = this;
1063 var selection = t.doc.getSelection();
1064 if (selection == null || selection.rangeCount <= 0) return null;
1065 var range = selection.getRangeAt(0);
1066 if(range == null) return null;
1067
1068 return range;
1069 }
1070
1071 getRangeText(range){
1072 return range + '';
1073 }
1074
1075 focus(){
1076 var t = this;
1077 if(t.hasFocus()) return;
1078 t.editor.click();
1079 t.editor.focus();
1080 t.restoreRange();
1081 }
1082
1083 getBounds(range){
1084 var rect = range.getBoundingClientRect();
1085 return {
1086 bottom: rect.top + rect.height,
1087 height: rect.height,
1088 left: rect.left,
1089 right: rect.right,
1090 top: rect.top,
1091 width: 0
1092 };
1093 }
1094
1095 showPen(jEle){
1096 var t = this;
1097 jEle = jEle || jQuery(t.toolbar);
1098
1099 var toolBar = jQuery(t.penHolder);
1100 var tooltipHeight = parseInt(toolBar.css('height'));
1101 var range = null;
1102
1103 if(! t.hasFocus() && t.range != null){
1104 range = t.range;
1105 }else{
1106 range = t.getRange();
1107 }
1108
1109 if(range == null){
1110 toolBar.hide();
1111 return;
1112 }
1113
1114 // Set left of toolbar
1115 var editorOffset = t.editor[0].getBoundingClientRect();
1116 var editorTop = editorOffset.top;
1117 var editorLeft = editorOffset.left;
1118 var editorbottom = editorTop + editorOffset.height - tooltipHeight;
1119 var toolBarTop = editorTop - 10;
1120 var bound = t.getBounds(range);
1121
1122 // Fallback: calculate bounds from range
1123 if(!bound || bound.height == 0 && bound.top == 0 && bound.left == 0){
1124 toolBar.hide();
1125 return;
1126 }
1127
1128 var boundTop = bound.top - 15;
1129
1130 // Set top of toolbar
1131 if( boundTop - tooltipHeight < 0 && bound.bottom > -5){
1132 toolBarTop = bound.bottom + tooltipHeight + 15;
1133 }else if( editorbottom - 30 < 0 ){
1134 toolBarTop = editorbottom + 20;
1135 }else if( toolBarTop - tooltipHeight < 0 ){
1136 toolBarTop = tooltipHeight + 10;
1137 }
1138
1139 // Show Toolbar
1140 toolBar.children().hide();
1141 toolBar.show();
1142 jEle.show();
1143
1144 // Set top of toolbar
1145 toolBar.css('top', toolBarTop);
1146
1147 // Set left of toobar
1148 var docW = jQuery(window).width() - 10;
1149 var toolW = toolBar.width();
1150 var edW = t.editor.width();
1151
1152 if(toolW > edW){
1153 editorLeft = editorLeft - (toolW - edW) / 2
1154 }
1155
1156 toolBar.css('left', editorLeft+'px');
1157
1158 var tooltipLeft = toolBar.offset().left;
1159
1160 if(tooltipLeft < 0){
1161 toolBar.css('left', '1px');
1162 }
1163
1164 var toolRight = tooltipLeft + toolW;
1165 if(docW < toolRight){
1166 toolBar.css('left', tooltipLeft - (toolRight - docW)+'px');
1167 }
1168
1169 }
1170
1171 getContent(){
1172 var editor = this.editor;
1173 var html = editor.html();
1174
1175 return html;
1176 }
1177
1178 setContent(html){
1179 var t = this;
1180 html = html || '';
1181 t.editor.html(html);
1182 t.editor.trigger('input');
1183 }
1184
1185 updateButtonStatus(){
1186 var t = this,
1187 toolbar = jQuery(t.toolbar),
1188 tags = t.getTagsRecursive(t.doc.getSelection().focusNode),
1189 activeClasses = 'pagelayer-pen-active';
1190
1191 jQuery('.' + activeClasses, toolbar).removeClass(activeClasses);
1192 jQuery.each(tags, function (i, tag){
1193 var btnName;
1194
1195 if(pagelayer_is_string(tag)){
1196 btnName = t.tagToButton[tag.toLowerCase()];
1197 }else{
1198 btnName = t.tagToButton[Object.keys(tag)[0].toLowerCase()]
1199 }
1200
1201 var $btn = jQuery('[data-format="'+btnName+'"]', toolbar);
1202
1203 if($btn.length < 1){
1204 return;
1205 }
1206
1207 if($btn.find('.pagelayer-pen-picker-label').length > 0){
1208 $btn.find('.pagelayer-pen-picker-label').addClass(activeClasses);
1209 return;
1210 }
1211
1212 $btn.addClass(activeClasses);
1213 });
1214 }
1215
1216 getTagsRecursive(element, tags) {
1217 var t = this;
1218 var jEle = jQuery(element);
1219 tags = tags || (element && element.tagName ? [element.tagName] : []);
1220
1221 if (element && element.parentNode) {
1222 element = element.parentNode;
1223 } else {
1224 return tags;
1225 }
1226
1227 var tag = element.tagName;
1228 // Is this editor
1229 if (tag === 'DIV') {
1230 return tags;
1231 }
1232
1233 // TODO: for all block element
1234 if (tag === 'P' && element.style.textAlign !== '') {
1235 tags.push(element.style.textAlign);
1236 }
1237
1238 jQuery.each(t.tagHandlers, function (i, tagHandler) {
1239 tags = tags.concat(tagHandler(element, t));
1240 });
1241
1242 tags.push(tag);
1243 var styles = jEle.attr('style');
1244
1245 if(!pagelayer_empty(styles)){
1246 var styles = styles.split(';');
1247
1248 jQuery.each(styles, function(i, style){
1249 style = style.split(':');
1250 var ss = String(style[0]).trim();
1251 var vv = String(style[1]).trim();
1252
1253 if(pagelayer_empty(ss) || ss in tags && !pagelayer_empty(tags[ss])){
1254 return;
1255 }
1256
1257 var obj = {};
1258 obj[ss] = vv;
1259
1260 tags.push(obj);
1261 });
1262 }
1263
1264 return t.getTagsRecursive(element, tags).filter(function (tag) {
1265 return tag != null;
1266 });
1267 }
1268
1269 buildDropdown(select){
1270
1271 var t = this;
1272 var fixIcon = '';
1273
1274 select = jQuery(select);
1275 var format = select.data('format');
1276
1277 var selAtts = '';
1278 var options = '';
1279 var optId = `pagelayer-pen-picker-options-${t.optionsCounter}`;
1280 t.optionsCounter += 1;
1281
1282 Array.from(select[0].attributes).forEach(item => {
1283 selAtts += ' '+item.name+'="'+ item.value +'"';
1284 });
1285
1286 Array.from(select[0].options).forEach(option => {
1287
1288 var attrs = '';
1289 var val = '';
1290 var itemInner = '';
1291
1292 if(option.hasAttribute('value')){
1293 val = option.getAttribute('value');
1294 attrs += ' data-value="'+val+'"';
1295 }
1296
1297 if(option.textContent){
1298 attrs += ' data-label="'+option.textContent+'"';
1299 }
1300
1301 // Set icon
1302 if('icon' in t.handlers[format] && typeof t.handlers[format]['icon'] == 'object' && !pagelayer_empty(t.handlers[format]['icon'][val])){
1303 itemInner = t.handlers[format]['icon'][val];
1304 }
1305
1306 options += `<span class="pagelayer-pen-picker-item" tabindex="0" role="button" ${attrs}>${itemInner}</span>`;
1307 });
1308
1309 if('fixIcon' in t.handlers[format]){
1310 fixIcon = t.handlers[format]['fixIcon'];
1311 }
1312
1313 var customInpute = '';
1314
1315 if('customInpute' in t.handlers[format] && !pagelayer_empty(t.handlers[format]['customInpute'])){
1316 customInpute = '<input type="text" class="pagelayer-pen-custom-input" placeholder="Custom value">';
1317 }
1318
1319 var container = jQuery(`<span ${selAtts}>
1320 <span class="pagelayer-pen-picker-label" tabindex="0" role="button" aria-expanded="false">${fixIcon}</span>
1321 <span class="pagelayer-pen-picker-options" aria-hidden="true" tabindex="-1" id="${optId}" aria-controls="${optId}">
1322 ${options}
1323 ${customInpute}
1324 </span>
1325 </span>`);
1326
1327 container.addClass('pagelayer-pen-picker');
1328
1329 select.before(container);
1330 select.hide();
1331
1332 var close = function(cEle){
1333 cEle.removeClass('pagelayer-pen-expanded');
1334 cEle.find('.pagelayer-pen-picker-label').attr('aria-expanded', 'false');
1335 cEle.find('.pagelayer-pen-picker-options').attr('aria-hidden', 'true');
1336 }
1337
1338 var selectItem = function(item, trigger = false){
1339 var selected = container.find('.pagelayer-pen-selected');
1340 var label = container.find('.pagelayer-pen-picker-label');
1341 var val = '';
1342
1343 if (item === selected) return;
1344 if (selected != null) {
1345 selected.removeClass('pagelayer-pen-selected');
1346 }
1347 if(item == null) return;
1348 item.classList.add('pagelayer-pen-selected');
1349 select.selectedIndex = Array.from(item.parentNode.children).indexOf(
1350 item,
1351 );
1352 if (item.hasAttribute('data-value')) {
1353 val = item.getAttribute('data-value');
1354 label.attr('data-value', val);
1355 } else {
1356 label.attr('data-value', val);
1357 }
1358 if (item.hasAttribute('data-label')) {
1359 label.attr('data-label', item.getAttribute('data-label'));
1360 } else {
1361 label.attr('data-label', '');
1362 }
1363
1364 if(!fixIcon){
1365 label.html(item.innerHTML);
1366 }
1367
1368 if(trigger) {
1369 select.val(val);
1370 select.trigger('change');
1371 close(container);
1372 }
1373 }
1374
1375 var toggleAriaAttribute = function(element, attribute) {
1376 element.setAttribute(
1377 attribute,
1378 !(element.getAttribute(attribute) === 'true'),
1379 );
1380 }
1381 var togglePicker = function() {
1382 container.toggleClass('pagelayer-pen-expanded');
1383 // Toggle aria-expanded and aria-hidden to make the picker accessible
1384 toggleAriaAttribute(container.find('.pagelayer-pen-picker-label')[0], 'aria-expanded');
1385 toggleAriaAttribute(container.find('.pagelayer-pen-picker-options')[0], 'aria-hidden');
1386 }
1387
1388 container.find('.pagelayer-pen-picker-item').on('click', function(){
1389 selectItem(this, true);
1390 close(container);
1391 });
1392
1393 container.find('.pagelayer-pen-picker-label').on('click', function(){
1394 togglePicker();
1395 });
1396
1397 container.find('.pagelayer-pen-custom-input').on('focusout keydown', function(e){
1398
1399 if(e.type == 'keydown' && e.keyCode != 13){
1400 return;
1401 }
1402
1403 e.preventDefault();
1404
1405 var val = jQuery(this).val();
1406
1407 if(pagelayer_empty(val)){
1408 return;
1409 }
1410
1411 var opt = select.find('option.pagelayer-pen-custom-value');
1412
1413 if(opt.length < 1){
1414 select.append('<option class="pagelayer-pen-custom-value"></option>');
1415 opt = select.find('option.pagelayer-pen-custom-value');
1416 }
1417
1418 opt.val(val);
1419 select.val(val);
1420 select.trigger('change');
1421 close(container);
1422 });
1423
1424 jQuery(t.toolbar).on('mousedown', function(e){
1425 var tEle = jQuery(this);
1426 var target = jQuery(e.target);
1427 var tPicker = target.closest('.pagelayer-pen-picker');
1428
1429 if(target.closest('.pagelayer-pen-picker-item').length > 0) return;
1430
1431 tEle.find('.pagelayer-pen-picker.pagelayer-pen-expanded').each(function(){
1432 var picker = jQuery(this);
1433 if(tPicker.length > 0 && tPicker.is(picker))return;
1434 close(picker);
1435 });
1436
1437 });
1438
1439 // TODO need to correct this function update the select
1440 container.on('update', function(){
1441 var item = container.find('.pagelayer-pen-selected');
1442
1443 if(item.length < 1){
1444 item = container.find('.pagelayer-pen-picker-item').first();
1445 }
1446
1447 selectItem(item[0]);
1448 });
1449
1450 container.trigger('update');
1451
1452 return container;
1453 }
1454
1455 buildColorBtnHandler(item){
1456 var t = this;
1457 var select = t.buildDropdown(item);
1458 var format = select.data('format');
1459
1460 // Set color
1461 select.find('.pagelayer-pen-picker-item').each(function(){
1462 var opt = jQuery(this);
1463 var color = opt.data('value');
1464
1465 opt.css({'background': color});
1466
1467 // TODO remove this and add on selecttion
1468 opt.on('click', function(){
1469 if(format == 'color'){
1470 opt.closest('.pagelayer-pen-picker-label').css({'text-color': color});
1471 }else{
1472 opt.closest('.pagelayer-pen-picker-label').css({'background-color': color});
1473 }
1474 });
1475 });
1476
1477 }
1478
1479 buildfontBtnHandler(item){
1480 var t = this;
1481
1482 var select = t.buildDropdown(item);
1483
1484 jQuery(item).on('change', function(){
1485 pagelayer_link_font_family(jQuery(this));
1486 });
1487
1488 }
1489
1490 setLinkHandler(){
1491 var t = this,
1492 documentSelection = t.doc.getSelection(),
1493 node = documentSelection.focusNode,
1494 text = new XMLSerializer().serializeToString(documentSelection.getRangeAt(0).cloneContents()),
1495 url = '',
1496 linkBtn = 'Link',
1497 unlinkBtn = 'Cancel';
1498
1499 while (['A', 'DIV'].indexOf(node.nodeName) < 0) {
1500 node = node.parentNode;
1501 }
1502
1503 if(node && node.nodeName === 'A'){
1504 var $a = jQuery(node);
1505 url = $a.attr('href');
1506 }
1507
1508 if(!pagelayer_empty(url)){
1509 linkBtn = 'Update';
1510 unlinkBtn = 'Unlink';
1511 }
1512
1513 t.saveRange();
1514
1515 var tooltip = this.addContainer('pagelayer-pen-link-tooltip');
1516 t.linkTooltip = tooltip;
1517
1518 var html = '<input type="text" name="url" placeholder="Enter URL ..." value="'+url+'" autocomplete="off"><span class="pagelayer-pen-link-btn pagelayer-btn-success">'+linkBtn+'</span><span class="pagelayer-pen-unlink-btn pagelayer-btn-primary">'+unlinkBtn+'</span>';
1519 tooltip.html(html);
1520
1521 var input = tooltip.find('input[name="url"]');
1522
1523 // Keep saving old range
1524 var metaRange = t.metaRange;
1525 var savedRange = t.range;
1526 var restoreRange = function(){
1527 t.metaRange = metaRange;
1528 t.range = savedRange;
1529 t.restoreRange();
1530 }
1531
1532 // Focus input immediately
1533 setTimeout(function(){
1534 input.focus();
1535 input[0].setSelectionRange(0, input.val().length);
1536 }, 50);
1537
1538 // Enter key to submit
1539 input.on('keydown', function(e){
1540 if(e.keyCode === 13){
1541 e.preventDefault();
1542 t.linkTooltip.find('.pagelayer-pen-link-btn').trigger('click');
1543 }
1544
1545 // Escape to close
1546 if(e.keyCode === 27){
1547 t.linkTooltip.find('.pagelayer-pen-unlink-btn').trigger('click');
1548 }
1549 });
1550
1551 t.linkTooltip.find('.pagelayer-pen-link-btn').on('click', function(){
1552 var url = input.val();
1553
1554 restoreRange();
1555 t.execCmd('createLink', url, true );
1556 t.editor.trigger('input');
1557 t.showPen();
1558 });
1559
1560 t.linkTooltip.find('.pagelayer-pen-unlink-btn').on('click', function(){
1561 restoreRange();
1562 if(unlinkBtn == 'Unlink'){
1563 t.execCmd('unlink', undefined, undefined, true);
1564 }
1565 t.showPen();
1566 });
1567
1568 // Click outside to close
1569 jQuery(document).on('click.pagelayerLinkTooltip', function(e){
1570 if(!jQuery(e.target).closest(t.linkTooltip).length && !jQuery(e.target).closest(t.editor).length){
1571 t.showPen();
1572 jQuery(document).off('click.pagelayerLinkTooltip');
1573 }
1574 });
1575
1576 t.showPen(t.linkTooltip);
1577 }
1578
1579 imageBtnHandler(){
1580 var t = this;
1581 t.destroyEd = false;
1582 t.destroy();
1583
1584 var frame = pagelayer_select_frame('image');
1585
1586 // On select update the stuff
1587 frame.on({'select': function(){
1588 var state = frame.state();
1589 var url = '', alt = '', id = '';
1590
1591 // External URL
1592 if('props' in state){
1593
1594 url = state.props.attributes.url;
1595 alt = state.props.attributes.alt;
1596
1597 // Internal from gallery
1598 }else{
1599
1600 var attachment = frame.state().get('selection').first().toJSON();
1601 //console.log(attachment);
1602
1603 // Set the new and URL
1604 url = attachment.url;
1605 alt = attachment.alt;
1606 id = attachment.id;
1607
1608 }
1609 t.editor.click();
1610 t.restoreRange();
1611 t.execCmd('insertImage', url, false, true);
1612 var $img = jQuery('img[src="' + url + '"]:not([alt])', t.editor);
1613
1614 $img.attr('alt', alt);
1615 $img.attr('pl-media-id', id);
1616 }
1617 });
1618
1619 frame.open();
1620 }
1621
1622 viewHTMLBtnHandler(param){
1623 var t = this;
1624 var html = t.getContent();
1625 t.destroyEd = false;
1626 t.destroy();
1627
1628 // Add Container
1629 var HTMLviewer = jQuery('.pagelayer-pen-html-viewer');
1630
1631 if(HTMLviewer.length < 1){
1632 jQuery('body').append('<div class="pagelayer-pen-html-viewer">'+
1633 '<div class="pagelayer-pen-html-holder">'+
1634 '<textarea class="pagelayer-pen-html-area"></textarea>'+
1635 '<div class="pagelayer-pen-html-btn">'+
1636 '<button class="pagelayer-pen-html-btn-update pagelayer-btn-success">Update</button>'+
1637 '<button class="pagelayer-pen-html-btn-cancel pagelayer-btn-secondary">Cancel</button>'+
1638 '</div>'+
1639 '</div>'+
1640 '</div>');
1641
1642 HTMLviewer = jQuery('.pagelayer-pen-html-viewer');
1643 }
1644
1645 HTMLviewer.find('.pagelayer-pen-html-area').val(html);
1646 HTMLviewer.show();
1647
1648 HTMLviewer.find('.pagelayer-pen-html-btn-update').unbind('click');
1649 HTMLviewer.find('.pagelayer-pen-html-btn-update').on('click', function(){
1650 var html = HTMLviewer.find('.pagelayer-pen-html-area').val();
1651 t.range = null;
1652 t.editor.click();
1653 t.setContent(html);
1654 t.editor.trigger('focus');
1655 HTMLviewer.hide();
1656 });
1657
1658 HTMLviewer.find('.pagelayer-pen-html-btn-cancel').unbind('click');
1659 HTMLviewer.find('.pagelayer-pen-html-btn-cancel').on('click', function(){
1660 t.editor.click();
1661 t.focus();
1662 HTMLviewer.hide();
1663 });
1664
1665 }
1666 }
1667