PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 2.0.3
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v2.0.3
4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / assets / js / preview.js
embedpress / assets / js Last commit date
vendor 9 years ago index.html 9 years ago preview.js 8 years ago settings.js 8 years ago
preview.js
1625 lines
1 /**
2 * @package EmbedPress
3 * @author EmbedPress <help@embedpress.com>
4 * @copyright Copyright (C) 2018 EmbedPress. All rights reserved.
5 * @license GPLv2 or later
6 * @since 1.0
7 */
8 (function($, String, $data, undefined) {
9 "use strict";
10
11 $(window.document).ready(function() {
12 String.prototype.capitalizeFirstLetter = function() {
13 return this.charAt(0).toUpperCase() + this.slice(1);
14 }
15
16 String.prototype.isValidUrl = function() {
17 var rule = /^(https?|embedpresss?):\/\//i;
18
19 return rule.test(this.toString());
20 }
21
22 String.prototype.hasShortcode = function(shortcode) {
23 var shortcodeRule = new RegExp('\\['+ shortcode +'(?:\\]|.+?\\])', "ig");
24 return !!this.toString().match(shortcodeRule);
25 }
26
27 String.prototype.stripShortcode = function(shortcode) {
28 var stripRule = new RegExp('(\\['+ shortcode +'(?:\\]|.+?\\])|\\[\\/'+ shortcode +'\\])', "ig");
29 return this.toString().replace(stripRule, "");
30 }
31
32 String.prototype.setShortcodeAttribute = function(attr, value, shortcode, replaceInsteadOfMerge) {
33 replaceInsteadOfMerge = typeof replaceInsteadOfMerge === "undefined" ? false : replaceInsteadOfMerge;
34 var subject = this.toString();
35
36 if (subject.hasShortcode(shortcode)) {
37 var attributes = subject.getShortcodeAttributes(shortcode);
38
39 if (attributes.hasOwnProperty(attr)) {
40 if (replaceInsteadOfMerge) {
41 attributes[attr] = value;
42 } else {
43 attributes[attr] += " " + value;
44 }
45 } else {
46 attributes[attr] = value;
47 }
48
49 if (!!Object.keys(attributes).length) {
50 var parsedAttributes = [];
51 for (var attr in attributes) {
52 parsedAttributes.push(attr + '="' + attributes[attr] + '"');
53 }
54
55 subject = '[' + shortcode + ' ' + parsedAttributes.join(" ") + ']' + subject.stripShortcode(shortcode) + '[/' + shortcode + ']';
56 } else {
57 subject = '[' + shortcode + ']' + subject.stripShortcode(shortcode) + '[/' + shortcode + ']';
58 }
59
60 return subject;
61 } else {
62 return subject;
63 }
64 }
65
66 String.prototype.getShortcodeAttributes = function(shortcode) {
67 var subject = this.toString();
68 if (subject.hasShortcode(shortcode)) {
69 var attributes = {};
70 var propertiesString = (new RegExp(/\[embed\s*(.*?)\]/ig)).exec(subject)[1]; // Separate all shortcode attributes from the rest of the string
71 if (propertiesString.length > 0) {
72 var extractAttributesRule = new RegExp(/(\!?\w+-?\w*)(?:="(.+?)")?/ig); // Extract attributes and their values
73 var match;
74 while (match = extractAttributesRule.exec(propertiesString)) {
75 var attrName = match[1];
76 var attrValue;
77 if (match[2] === undefined) {
78 // Prevent `class` property being empty an treated as a boolean param
79 if (attrName.toLowerCase() !== "class") {
80 if (attrName.indexOf('!') === 0) {
81 attrName = attrName.replace('!', "");
82 attrValue = "false";
83 } else {
84 attrValue = "true";
85 }
86
87 attributes[attrName] = attrValue;
88 }
89 } else {
90 attrValue = match[2];
91 if (attrValue.isBoolean()) {
92 attrValue = attrValue.isFalse() ? "false" : "true";
93 }
94
95 attributes[attrName] = attrValue;
96 }
97 }
98 match = extractAttributesRule = null;
99 }
100 propertiesString = null;
101
102 return attributes;
103 } else {
104 return {};
105 }
106 }
107
108 String.prototype.isBoolean = function() {
109 var subject = this.toString().trim().toLowerCase();
110
111 return subject.isTrue(false) || subject.isFalse();
112 };
113
114 String.prototype.isTrue = function(defaultValue) {
115 var subject = this.toString().trim().toLowerCase();
116 defaultValue = typeof defaultValue === undefined ? true : defaultValue;
117
118 switch (subject) {
119 case "":
120 defaultValue += "";
121 return !defaultValue.isFalse();
122 case "1":
123 case "true":
124 case "on":
125 case "yes":
126 case "y":
127 return true;
128 default:
129 return false;
130 }
131 };
132
133 String.prototype.isFalse = function() {
134 var subject = this.toString().trim().toLowerCase();
135
136 switch (subject) {
137 case "0":
138 case "false":
139 case "off":
140 case "no":
141 case "n":
142 case "nil":
143 case "null":
144 return true;
145 default:
146 return false;
147 }
148 };
149
150 var SHORTCODE_REGEXP = new RegExp('\\[\/?'+ $data.EMBEDPRESS_SHORTCODE +'\\]', "gi");
151
152 var EmbedPress = function() {
153 var self = this;
154
155 var PLG_SYSTEM_ASSETS_CSS_PATH = $data.EMBEDPRESS_URL_ASSETS +"css";
156 var PLG_CONTENT_ASSETS_CSS_PATH = PLG_SYSTEM_ASSETS_CSS_PATH;
157
158 /**
159 * The default params
160 *
161 * @type Object
162 */
163 self.params = {
164 baseUrl : '',
165 versionUID: '0'
166 };
167
168 /**
169 * True, if user agent is iOS
170 * @type Boolean True, if is iOS
171 */
172 self.iOS = /iPad|iPod|iPhone/.test(window.navigator.userAgent);
173
174 /**
175 * The active wrapper, activated by the mouse enter event
176 * @type Element
177 */
178 self.activeWrapper = null;
179
180 self.activeWrapperForModal = null;
181
182 /**
183 * The active controller panel
184 * @type Element
185 */
186 self.activeControllerPanel = null;
187
188 /**
189 * A list containing all loaded editor instances on the page
190 * @type Array
191 */
192 self.loadedEditors = [];
193
194 /**
195 * Init the plugin
196 *
197 * @param object params Override the plugin's params
198 * @return void
199 */
200 self.init = function (params) {
201 $.extend(self.params, params);
202
203 // Fix iOS doesn't firing click events on 'standard' elements
204 if (self.iOS) {
205 $(window.document.body).css('cursor', 'pointer');
206 }
207
208 $(self.onReady);
209 };
210
211 self.addEvent = function(event, element, callback) {
212 if (typeof element.on !== 'undefined') {
213 element.on(event, callback);
214 } else {
215 if (element['on' + event.capitalizeFirstLetter()]) {
216 element['on' + event.capitalizeFirstLetter()].add(callback);
217 }
218 }
219 };
220
221 self.isEmpty = function(list) {
222 return list.length === 0;
223 };
224
225 self.isDefined = function(attribute) {
226 return (typeof attribute !== 'undefined') && (attribute !== null);
227 }
228
229 self.makeId = function() {
230 var text = "";
231 var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
232
233 for( var i=0; i < 5; i++ )
234 text += possible.charAt(Math.floor(Math.random() * possible.length));
235
236 return text;
237 };
238
239 self.loadAsyncDynamicJsCodeFromElement = function(subject, wrapper, editorInstance)
240 {
241 subject = $(subject);
242 if (subject.prop('tagName').toLowerCase() === "script") {
243 var scriptSrc = subject.attr('src') || null;
244 if (!scriptSrc) {
245 self.addScriptDeclaration(wrapper, subject.html(), editorInstance);
246 } else {
247 self.addScript(scriptSrc, null, wrapper, editorInstance);
248 }
249 } else {
250 var innerScriptsList = $('script', subject);
251 if (innerScriptsList.length > 0) {
252 $.each(innerScriptsList, function(innerScriptIndex, innerScript) {
253 self.loadAsyncDynamicJsCodeFromElement(innerScript, wrapper, editorInstance);
254 });
255 }
256 }
257 }
258
259 /**
260 * Method executed on the document ready event
261 *
262 * @return void
263 */
264 self.onReady = function() {
265 if (self.tinymceIsAvailable()) {
266 // Wait until the editor is available
267 var interval = window.setInterval(
268 function() {
269 var editorsFound = self.getEditors();
270 if (editorsFound.length) {
271 self.loadedEditors = editorsFound;
272
273 for (var editorIndex = 0; editorIndex < self.loadedEditors.length; editorIndex++) {
274 self.onFindEditor(self.loadedEditors[editorIndex]);
275 }
276
277 window.clearInterval(interval);
278
279 return self.loadedEditors;
280 }
281 },
282 250
283 );
284 }
285 };
286
287 /**
288 * Detects if tinymce object is available
289 * @return Boolean True, if available
290 */
291 self.tinymceIsAvailable = function() {
292 return typeof window.tinymce === 'object' || typeof window.tinyMCE === "object";
293 }
294
295 /**
296 * Returns true if the controller panel is active
297 * @return Boolean True, if the controller panel is active
298 */
299 self.controllerPanelIsActive = function() {
300 return typeof self.activeControllerPanel !== 'undefined' && self.activeControllerPanel !== null;
301 };
302
303 /**
304 * Returns the editor
305 * @return Object The editor
306 */
307 self.getEditors = function() {
308 if (!window.tinymce || !window.tinymce.editors || window.tinymce.editors.length === 0) {
309 return [];
310 }
311
312 return window.tinymce.editors || [];
313 };
314
315 /**
316 * Parses the content, sending it to the component which will
317 * look for urls to be parsed into embed codes
318 *
319 * @param string content The content
320 * @param function onsuccess The callback called on success
321 * @return void
322 */
323 self.getParsedContent = function(content, onsuccess) {
324 // Get the parsed content
325 $.ajax({
326 type: 'POST',
327 url: self.params.baseUrl +"wp-admin/admin-ajax.php",
328 data: {
329 action: "embedpress_do_ajax_request",
330 subject: content
331 },
332 success: onsuccess,
333 dataType: 'json',
334 async: true
335 });
336 };
337
338 self.addStylesheet = function(url, editorInstance) {
339 var head = editorInstance.getDoc().getElementsByTagName('head')[0];
340
341 var $style = $('<link rel="stylesheet" type="text/css" href="' + url + '">');
342 $style.appendTo(head);
343 }
344
345 self.convertURLSchemeToPattern = function(scheme) {
346 var prefix = '(.*)((?:http|embedpress)s?:\\/\\/(?:www\\.)?',
347 suffix = '[\\/]?)(.*)',
348 pattern;
349
350 scheme = scheme.replace(/\*/g, '[a-zA-Z0-9=&_\\-\\?\\.\\/!\\+%:@,#]+');
351 scheme = scheme.replace(/\./g, '\\.');
352 scheme = scheme.replace(/\//g, '\\/');
353
354 return prefix + scheme + suffix;
355 };
356
357 self.getProvidersURLPatterns = function() {
358 // @todo: Add option to disable/enable the providers
359 var patterns = [];
360
361 self.each($data.urlSchemes, function convertEachURLSchemesToPattern(scheme) {
362 patterns.push(self.convertURLSchemeToPattern(scheme));
363 });
364
365 return patterns;
366 };
367
368 self.addScript = function(source, callback, wrapper, editorInstance) {
369 var doc = editorInstance.getDoc();
370
371 if (typeof wrapper === 'undefined' || !wrapper) {
372 wrapper = $(doc.getElementsByTagName('head')[0]);
373 }
374
375 var $script = $(doc.createElement('script'));
376 $script.attr('async', 1);
377
378 if (typeof callback === 'function') {
379 $script.ready(callback);
380 }
381
382 $script.attr('src', source);
383
384 wrapper.append($script);
385 };
386
387 self.addScriptDeclaration = function(wrapper, declaration, editorInstance) {
388 var doc = editorInstance.getDoc(),
389 $script = $(doc.createElement('script'));
390
391 $(wrapper).append($script);
392
393 $script.text(declaration);
394 };
395
396 self.addURLsPlaceholder = function(node, url, editorInstance) {
397 var uid = self.makeId();
398
399 var wrapperClasses = ["embedpress_wrapper", "embedpress_placeholder", "wpview", "wpview-wrap"];
400
401 var shortcodeAttributes = node.value.getShortcodeAttributes($data.EMBEDPRESS_SHORTCODE);
402 var customAttributes = shortcodeAttributes;
403
404 var customClasses = "";
405 if (!!Object.keys(shortcodeAttributes).length) {
406 var specialAttributes = ["class", "href", "data-href"];
407 // Iterates over each attribute of shortcodeAttributes to add the prefix "data-" if missing
408 var dataPrefix = "data-";
409 var prefixedShortcodeAttributes = [];
410 for (var attr in shortcodeAttributes) {
411 if (specialAttributes.indexOf(attr) === -1) {
412 if (attr.indexOf(dataPrefix) !== 0) {
413 prefixedShortcodeAttributes[dataPrefix + attr] = shortcodeAttributes[attr];
414 } else {
415 prefixedShortcodeAttributes[attr] = shortcodeAttributes[attr];
416 }
417 } else {
418 attr = attr.replace(dataPrefix, "");
419 if (attr === "class") {
420 wrapperClasses.push(shortcodeAttributes[attr]);
421 }
422 }
423 }
424
425 shortcodeAttributes = prefixedShortcodeAttributes;
426 prefixedShortcodeAttributes = dataPrefix = null;
427 }
428
429 if (("data-width" in shortcodeAttributes || "data-height" in shortcodeAttributes) && "data-responsive" in shortcodeAttributes) {
430 shortcodeAttributes['data-responsive'] = "false";
431 }
432
433 var wrapper = new self.Node('div', 1);
434 var wrapperSettings = {
435 'class' : Array.from(new Set(wrapperClasses)).join(" "),
436 'data-url' : url,
437 'data-uid' : uid,
438 'id' : 'embedpress_wrapper_' + uid,
439 'data-loading-text': 'Loading your embed...'
440 };
441
442 wrapperSettings = $.extend({}, wrapperSettings, shortcodeAttributes);
443
444 if (wrapperSettings.class.indexOf('is-loading') === -1) {
445 wrapperSettings.class += " is-loading";
446 }
447
448 wrapper.attr(wrapperSettings);
449
450 var panel = new self.Node('div', 1);
451 panel.attr({
452 'id' : 'embedpress_controller_panel_' + uid,
453 'class': 'embedpress_controller_panel embedpress_ignore_mouseout hidden'
454 });
455 wrapper.append(panel);
456
457 function createGhostNode(htmlTag, content) {
458 htmlTag = htmlTag || "span";
459 content = content || "&nbsp;";
460
461 var ghostNode = new self.Node(htmlTag, 1);
462 ghostNode.attr({
463 'class': "hidden"
464 });
465
466 var ghostText = new self.Node('#text', 3);
467 ghostText.value = content;
468 ghostNode.append(ghostText);
469
470 return ghostNode;
471 }
472
473 var editButton = new self.Node('div', 1);
474 editButton.attr({
475 'id' : 'embedpress_button_edit_' + uid,
476 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
477 });
478 var editButtonIcon = new self.Node('div', 1);
479 editButtonIcon.attr({
480 'class': 'embedpress-icon-pencil embedpress_ignore_mouseout'
481 });
482 editButtonIcon.append(createGhostNode());
483 editButton.append(editButtonIcon);
484 panel.append(editButton);
485
486 var removeButton = new self.Node('div', 1);
487 removeButton.attr({
488 'id' : 'embedpress_button_remove_' + uid,
489 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
490 });
491 var removeButtonIcon = new self.Node('div', 1);
492 removeButtonIcon.attr({
493 'class': 'embedpress-icon-x embedpress_ignore_mouseout'
494 });
495 removeButtonIcon.append(createGhostNode());
496 removeButton.append(removeButtonIcon);
497 panel.append(removeButton);
498
499 node.value = node.value.trim();
500
501 node.replace(wrapper);
502
503 // Trigger the timeout which will load the content
504 window.setTimeout(function() {
505 self.parseContentAsync(uid, url, customAttributes, editorInstance);
506 }, 200);
507
508 return wrapper;
509 };
510
511 self.parseContentAsync = function(uid, url, customAttributes, editorInstance) {
512 customAttributes = typeof customAttributes === "undefined" ? {} : customAttributes;
513
514 url = self.decodeEmbedURLSpecialChars(url, true, customAttributes);
515 var rawUrl = url.stripShortcode($data.EMBEDPRESS_SHORTCODE);
516
517 $(self).triggerHandler('EmbedPress.beforeEmbed', {
518 'url' : rawUrl,
519 'meta': {
520 'attributes': customAttributes || {}
521 }
522 });
523
524 // Get the parsed embed code from the EmbedPress plugin
525 self.getParsedContent(url, function getParsedContentCallback(result) {
526 var embeddedContent = (typeof result.data === "object" ? result.data.embed : result.data).stripShortcode($data.EMBEDPRESS_SHORTCODE);
527
528 var $wrapper = $(self.getElementInContentById('embedpress_wrapper_' + uid, editorInstance));
529 var wrapperParent = $($wrapper.parent());
530
531 // Check if $wrapper was rendered inside a <p> element.
532 if (wrapperParent.prop('tagName') && wrapperParent.prop('tagName').toUpperCase() === "P") {
533 wrapperParent.replaceWith($wrapper);
534 // Check if there's at least one "space" after $wrapper.
535 var nextSibling = $($wrapper).next();
536 if (!nextSibling.length || nextSibling.prop('tagName').toUpperCase() !== "P") {
537 //$('<p>&nbsp;</p>').insertAfter($wrapper);
538 }
539 nextSibling = null;
540 }
541 wrapperParent = null;
542
543 // Check if the url could not be embedded for some reason.
544 if (rawUrl === embeddedContent) {
545 // Echoes the raw url
546 $wrapper.replaceWith($('<p>'+ rawUrl +'</p>'));
547 return;
548 }
549
550 $wrapper.removeClass('is-loading');
551
552 // Parse as DOM element
553 var $content;
554 try {
555 $content = $(embeddedContent);
556 } catch(err) {
557 // Fallback to a div, if the result is not a html markup, e.g. a url
558 $content = $('<div>');
559 $content.html(embeddedContent);
560 }
561
562 if (!$('iframe', $content).length) {
563 var contentWrapper = $($content).clone();
564 contentWrapper.html('');
565
566 var dom = editorInstance.dom;
567
568 $wrapper.removeClass('embedpress_placeholder');
569
570 $wrapper.append(contentWrapper);
571
572 setTimeout(function() {
573 editorInstance.undoManager.transact(function() {
574 var iframe = editorInstance.getDoc().createElement('iframe');
575 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
576 iframe.frameBorder = '0';
577 iframe.allowTransparency = 'true';
578 iframe.scrolling = 'no';
579 iframe.class = "wpview-sandbox";
580 iframe.style.width = '100%';
581
582 dom.add(contentWrapper, iframe);
583 var iframeWindow = iframe.contentWindow;
584 // Content failed to load.
585 if (!iframeWindow) {
586 return;
587 }
588
589 var iframeDoc = iframeWindow.document;
590
591 $(iframe).load(function() {
592 var maximumChecksAllowed = 8;
593 var checkIndex = 0;
594
595 var checkerInterval = setInterval(function() {
596 if (checkIndex === maximumChecksAllowed) {
597 clearInterval(checkerInterval);
598
599 setTimeout(function() {
600 $wrapper.css('width', iframe.width);
601 $wrapper.css('height', iframe.height);
602 }, 100);
603 } else {
604 if (customAttributes.height) {
605 iframe.height = customAttributes.height;
606 iframe.style.height = customAttributes.height +'px';
607 } else {
608 iframe.height = $('body', iframeDoc).height();
609 }
610
611 if (customAttributes.width) {
612 iframe.width = customAttributes.width;
613 iframe.style.width = customAttributes.width +'px';
614 } else {
615 iframe.width = $('body', iframeDoc).width();
616 }
617
618 checkIndex++;
619 }
620 }, 250);
621 });
622
623 iframeDoc.open();
624 iframeDoc.write(
625 '<!DOCTYPE html>'+
626 '<html>'+
627 '<head>'+
628 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'+
629 '<style>'+
630 'html {'+
631 'background: transparent;'+
632 'padding: 0;'+
633 'margin: 0;'+
634 '}'+
635 'body#wpview-iframe-sandbox {'+
636 'background: transparent;'+
637 'padding: 1px 0 !important;'+
638 'margin: -1px 0 0 !important;'+
639 '}'+
640 'body#wpview-iframe-sandbox:before,'+
641 'body#wpview-iframe-sandbox:after {'+
642 'display: none;'+
643 'content: "";'+
644 '}'+
645 '</style>'+
646 '</head>'+
647 '<body id="wpview-iframe-sandbox" class="'+ editorInstance.getBody().className +'" style="display: inline-block;">'+
648 $content.html() +
649 '</body>'+
650 '</html>'
651 );
652 iframeDoc.close();
653 });
654 }, 50);
655 } else {
656 $wrapper.removeClass('embedpress_placeholder');
657
658 self.appendElementsIntoWrapper($content, $wrapper, editorInstance);
659 }
660
661 $wrapper.append($('<span class="wpview-end"></span>'));
662
663 if (result && result.data && typeof result.data === "object") {
664 result.data.width = $($wrapper).width();
665 result.data.height = $($wrapper).height();
666 }
667
668 $(self).triggerHandler('EmbedPress.afterEmbed', {
669 'meta' : result.data,
670 'url' : rawUrl,
671 'wrapper': $wrapper
672 });
673 });
674 };
675
676 self.appendElementsIntoWrapper = function(elementsList, wrapper, editorInstance) {
677 if (elementsList.length > 0) {
678 $.each(elementsList, function appendElementIntoWrapper(elementIndex, element) {
679 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
680 if (element.tagName.toLowerCase() !== 'script') {
681 wrapper.append($(element));
682
683 if (element.tagName.toLowerCase() === 'iframe') {
684 $(element).ready(function() {
685 window.setTimeout(function() {
686 $.each(editorInstance.dom.select('div.embedpress_wrapper iframe'), function(elementIndex, iframe) {
687 self.fixIframeSize(iframe);
688 });
689 }, 300);
690 });
691 } else if (element.tagName.toLowerCase() === "div") {
692 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
693 // This ensures that the embed wrapper have the same width as its content
694 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
695 }
696
697 $(element).css('max-width', $($(element).parents('body').get(0)).width());
698 }
699 }
700
701 self.loadAsyncDynamicJsCodeFromElement(element, wrapper, editorInstance);
702 });
703 }
704
705 return wrapper;
706 };
707
708 self.encodeEmbedURLSpecialChars = function(content) {
709 if (content.match(SHORTCODE_REGEXP)) {
710 var subject = content.replace(SHORTCODE_REGEXP, '');
711
712 if (!subject.isValidUrl()) {
713 return content;
714 }
715
716 content = subject;
717 subject = null;
718 }
719
720 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
721 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
722
723 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
724 content = content.replace('@', '::__at__::').trim();
725
726 return content;
727 };
728
729 self.decodeEmbedURLSpecialChars = function(content, applyShortcode, attributes) {
730 var encodingRegexpRule = /embedpress(s?):\/\//;
731 applyShortcode = (typeof applyShortcode === "undefined") ? true : applyShortcode;
732 attributes = (typeof attributes === "undefined") ? {} : attributes;
733
734 var isEncoded = content.match(encodingRegexpRule);
735
736 // Restore http[s] in the url (converted to bypass autolink plugin)
737 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
738 content = content.replace('::__at__::', '@').trim();
739
740 if ("class" in attributes) {
741 var classesList = attributes.class.split(/\s/g);
742 var shouldRemoveDynamicWidthClass = false;
743 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
744 if (classesList[classIndex] === "dynamic-width") {
745 shouldRemoveDynamicWidthClass = classIndex;
746 break;
747 }
748 }
749
750 if (shouldRemoveDynamicWidthClass !== false) {
751 classesList.splice(shouldRemoveDynamicWidthClass, 1);
752
753 if (classesList.length === 0) {
754 delete attributes.class;
755 }
756
757 attributes.class = classesList.join(" ");
758 }
759
760 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
761 }
762
763 if (isEncoded && applyShortcode) {
764 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE;
765 if (!!Object.keys(attributes).length) {
766 var attrValue;
767
768 for (var attrName in attributes) {
769 attrValue = attributes[attrName];
770
771 // Prevent `class` property being empty an treated as a boolean param
772 if (attrName.toLowerCase() === "class" && !attrValue.length) {
773 continue;
774 }
775 else {
776 if (attrValue.isBoolean()) {
777 shortcode += " ";
778 if (attrValue.isFalse()) {
779 shortcode += "!";
780 }
781
782 shortcode += attrName
783 } else {
784 shortcode += ' '+ attrName +'="'+ attrValue +'"';
785 }
786 }
787 }
788 attrValue = attrName = null;
789 }
790
791 content = shortcode + ']' + content + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
792 }
793
794 return content;
795 };
796
797 /**
798 * Method executed after find the editor. It will make additional
799 * configurations and add the content's stylesheets for the preview
800 *
801 * @return void
802 */
803 self.onFindEditor = function(editorInstance) {
804 self.each = tinymce.each;
805 self.extend = tinymce.extend;
806 self.JSON = tinymce.util.JSON;
807 self.Node = tinymce.html.Node;
808
809 function onFindEditorCallback() {
810 $(window.document.getElementsByTagName('head')[0]).append($('<link rel="stylesheet" type="text/css" href="' + (PLG_SYSTEM_ASSETS_CSS_PATH + '/vendor/bootstrap/bootstrap.min.css?v=' + self.params.versionUID) + '">'));
811
812 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID, editorInstance, editorInstance);
813 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID, editorInstance, editorInstance);
814 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID, editorInstance, editorInstance);
815 self.addEvent('nodechange', editorInstance, self.onNodeChange);
816 self.addEvent('keydown', editorInstance, function(e) {
817 self.onKeyDown(e, editorInstance);
818 });
819
820 var onUndoCallback = function(e) {
821 self.onUndo(e, editorInstance);
822 };
823
824 self.addEvent('undo', editorInstance, onUndoCallback); // TinyMCE
825 self.addEvent('undo', editorInstance.undoManager, onUndoCallback); // JCE
826
827 var doc = editorInstance.getDoc();
828 $(doc).on('mouseenter', '.embedpress_wrapper', function(e) {
829 self.onMouseEnter(e, editorInstance);
830 });
831 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
832 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', function(e) {
833 self.cancelEvent(e, editorInstance)
834 });
835 doc = null;
836
837 // Add the node filter that will convert the url into the preview box for the embed code
838 editorInstance.parser.addNodeFilter('#text', function addNodeFilterIntoParser(nodes, arg) {
839 self.each(nodes, function eachNodeInParser(node) {
840 var subject = node.value.trim();
841
842
843 if (!subject.isValidUrl()) {
844 if (!subject.match(SHORTCODE_REGEXP)) {
845 return;
846 }
847 }
848 subject = self.decodeEmbedURLSpecialChars(subject);
849 if (!subject.isValidUrl()) {
850 if (!subject.match(SHORTCODE_REGEXP)) {
851 return;
852 }
853 }
854
855
856 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
857
858 // These patterns need to have groups for the pre and post texts
859 // @TODO: maybe remove this list of URLs? Let the server side code decide what URL should be parsed
860 var patterns = self.getProvidersURLPatterns();
861
862 (function tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex) {
863 if (urlPatternIndex < patterns.length) {
864 var urlPattern = patterns[urlPatternIndex];
865 var urlPatternRegex = new RegExp(urlPattern);
866
867 var url = self.decodeEmbedURLSpecialChars(subject).trim();
868
869 var matches = url.match(urlPatternRegex);
870 // Check if content matches the url pattern.
871 if (matches && matches !== null && !!matches.length) {
872 url = self.encodeEmbedURLSpecialChars(matches[2]);
873
874 var wrapper = self.addURLsPlaceholder(node, url, editorInstance);
875
876 setTimeout(function() {
877 var doc = editorInstance.getDoc();
878
879 var previewWrapper = $(doc.querySelector('#'+ wrapper.attributes.map['id']));
880 var previewWrapperParent = $(previewWrapper.parent());
881
882 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === "P") {
883 previewWrapperParent.replaceWith(previewWrapper);
884 }
885
886 var previewWrapperOlderSibling = previewWrapper.prev();
887 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === "P" && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
888 previewWrapperOlderSibling.remove();
889 } else {
890 if (typeof previewWrapperOlderSibling.html() !== 'undefined') {
891 if (previewWrapperOlderSibling.html().match(/<[\/]?br>/)) {
892 if (!previewWrapperOlderSibling.prev().length) {
893 previewWrapperOlderSibling.remove();
894 }
895 }
896 }
897 }
898
899 var previewWrapperYoungerSibling = previewWrapper.next();
900 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === "P") {
901 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
902 previewWrapperYoungerSibling.remove();
903 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
904 }
905 } else {
906 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
907 }
908
909 setTimeout(function() {
910 editorInstance.selection.select(editorInstance.getBody(), true);
911 editorInstance.selection.collapse(false);
912 }, 50);
913 }, 50);
914 } else {
915 // No match. So we move on to check the next url pattern.
916 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
917 }
918 }
919 })(0);
920 });
921 });
922
923 // Add the filter that will convert the preview box/embed code back to the raw url
924 editorInstance.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer(nodes, arg) {
925 self.each(nodes, function eachNodeInSerializer(node) {
926 var nodeClasses = (node.attributes.map.class || "").split(' ');
927 var wrapperFactoryClasses = ["embedpress_wrapper", "embedpress_placeholder", "wpview", "wpview-wrap"];
928
929 var isWrapped = nodeClasses.filter(function(n) {
930 return wrapperFactoryClasses.indexOf(n) != -1;
931 }).length > 0;
932
933 if (isWrapped) {
934 var factoryAttributes = ["id", "style", "data-loading-text", "data-uid", "data-url"];
935 var customAttributes = {};
936 var dataPrefix = "data-";
937 for (var attr in node.attributes.map) {
938 if (attr.toLowerCase() !== "class") {
939 if (factoryAttributes.indexOf(attr) < 0) {
940 // Remove the "data-" prefix for more readability
941 customAttributes[attr.replace(dataPrefix, "")] = node.attributes.map[attr];
942 }
943 } else {
944 var customClasses = [];
945 for (var wrapperClassIndex in nodeClasses) {
946 var wrapperClass = nodeClasses[wrapperClassIndex];
947 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
948 customClasses.push(wrapperClass);
949 }
950 }
951
952 if (!!customClasses.length) {
953 customAttributes.class = customClasses.join(" ");
954 }
955 }
956 }
957
958 var p = new self.Node('p', 1);
959
960 var text = new self.Node('#text', 3);
961 text.value = self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes);
962
963 p.append(text.clone());
964
965 node.replace(text);
966 text.replace(p);
967 }
968 });
969 });
970
971 editorInstance.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer(nodes, arg) {
972 self.each(nodes, function eachNodeInSerializer(node) {
973 if (node.firstChild == node.lastChild) {
974 if (node.firstChild && "value" in node.firstChild && (node.firstChild.value === "&nbsp;" || !node.firstChild.value.trim().length)) {
975 node.remove();
976 }
977 }
978 });
979 });
980
981 //@todo:isthiseachreallynecessary?
982 // Add event to reconfigure wrappers every time the content is loaded
983 tinymce.each(tinymce.editors, function onEachEditor(editor) {
984 self.addEvent('loadContent', editor, function onInitEditor(ed) {
985 self.configureWrappers(editor);
986 });
987 });
988
989 // Add the edit form
990
991 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
992 window.setTimeout(
993 function afterTimeoutOnFindEditor() {
994 /*
995 * This is required because after load/refresh the page, the
996 * onLoadContent is not being triggered automatically, so
997 * we force the event
998 */
999 editorInstance.load();
1000 },
1001 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1002 500
1003 );
1004 }
1005
1006 // Let's make sure the inner doc has been fully loaded first.
1007 var checkTimesLimit = 100;
1008 var checkIndex = 0;
1009 var statusCheckerInterval = setInterval(function() {
1010 if (checkIndex === checkTimesLimit) {
1011 clearInterval(statusCheckerInterval);
1012 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1013 } else {
1014 var doc = editorInstance.getDoc();
1015 if (doc) {
1016 clearInterval(statusCheckerInterval);
1017 onFindEditorCallback();
1018 } else {
1019 checkIndex++;
1020 }
1021 }
1022 }, 250);
1023 };
1024
1025 self.fixIframeSize = function(iframe) {
1026 var maxWidth = 480;
1027 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1028 var ratio = $(iframe).height() / $(iframe).width();
1029 $(iframe).width(maxWidth);
1030 $(iframe).height(maxWidth * ratio);
1031 $(iframe).css('max-width', maxWidth);
1032 $(iframe).attr('max-width', maxWidth);
1033
1034 $(iframe).data('size-fixed', true);
1035 }
1036 }
1037
1038 /**
1039 * Function triggered on mouse enter the wrapper
1040 *
1041 * @param object e The event
1042 * @return void
1043 */
1044 self.onMouseEnter = function(e, editorInstance) {
1045 self.displayPreviewControllerPanel($(e.currentTarget), editorInstance);
1046 };
1047
1048 /**
1049 * Function triggered on mouse get out of the wrapper
1050 *
1051 * @param object e The event
1052 * @return void
1053 */
1054 self.onMouseOut = function(e) {
1055 // Check if the destiny is not a child element
1056 // Chrome
1057 if (self.isDefined(e.toElement)) {
1058 if (e.toElement.parentElement == e.fromElement
1059 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1060 ) {
1061 return false;
1062 }
1063 }
1064
1065 // Firefox
1066 if (self.isDefined(e.relatedTarget)) {
1067 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1068 return false;
1069 }
1070 }
1071
1072 self.hidePreviewControllerPanel();
1073 };
1074
1075 /**
1076 * Callback triggered by paste events. This should be hooked by TinyMCE's paste_preprocess
1077 * setting. A normal bind to the onPaste event doesn't work correctly all the times
1078 * (specially when you copy and paste content from the same editor).
1079 *
1080 * @param mixed - plugin
1081 * @param mixed - args
1082 *
1083 * @return void
1084 */
1085
1086 self.onPaste = function(plugin, args) {
1087 var urlPatternRegex = new RegExp(/(https?):\/\/([w]{3}\.)?.+?(?:\s|$)/i);
1088 var urlPatternsList = self.getProvidersURLPatterns();
1089
1090 // Split the pasted content into separated lines.
1091 var contentLines = args.content.split(/\n/g) || [];
1092 contentLines = contentLines.map(function(line, itemIndex) {
1093 // Check if there's a url into `line`.
1094 if (line.match(urlPatternRegex)) {
1095 // Split the current line across its space-characters to isolate the url.
1096 let termsList = line.trim().split(/\s+/);
1097 termsList = termsList.map(function(term, termIndex) {
1098 // Check if the term into the current line is a url.
1099 var match = term.match(urlPatternRegex);
1100 if (match) {
1101 for (var urlPatternIndex = 0; urlPatternIndex < urlPatternsList.length; urlPatternIndex++) {
1102 // Isolates that url from the rest of the content if the service is supported.
1103 var urlPattern = new RegExp(urlPatternsList[urlPatternIndex]);
1104 if (urlPattern.test(term)) {
1105 return '</p><p>'+ match[0] +'</p><p>';
1106 }
1107 }
1108 }
1109
1110 return term;
1111 });
1112
1113 termsList[termsList.length - 1] = termsList[termsList.length - 1] + '<br>';
1114
1115 line = termsList.join(' ');
1116 }
1117
1118 return line;
1119 });
1120
1121 // Check if the text was transformed or not. If it was, add wrappers
1122 var content = contentLines.join('');
1123
1124 if (content.replace(/<br>$/, '') !== args.content) {
1125 args.content = '<p>'+ args.content +'</p>';
1126 }
1127 };
1128
1129 /**
1130 * Method trigered on every node change, to detect new lines. It will
1131 * try to fix a default behavior for some editors of clone the parent
1132 * element when adding a line break. This will clone the embed wrapper
1133 * if we set the cursor after a preview wrapper and hit enter.
1134 *
1135 * @param object e The event
1136 * @return void
1137 */
1138 self.onNodeChange = function(e) {
1139 // Fix the clone parent on break lines issue
1140 // Check if a line break was added
1141 if (e.element.tagName === 'BR') {
1142 // Check one of the parent elements is a clonned embed wrapper
1143 if (e.parents.length > 0) {
1144 $.each(e.parents, function(index, parent) {
1145 if ($(parent).hasClass('embedpress_wrapper')) {
1146 // Remove the cloned wrapper and replace with a 'br' tag
1147 $(parent).replaceWith($('<br>'));
1148 }
1149 });
1150 }
1151 } else if (e.element.tagName === "IFRAME") {
1152 if (e.parents.length > 0) {
1153 $.each(e.parents, function(index, parent) {
1154 parent = $(parent);
1155 if (parent.hasClass('embedpress_wrapper')) {
1156 var wrapper = $('.embedpress-wrapper', parent);
1157 if (wrapper.length > 1) {
1158 wrapper.get(0).remove();
1159 }
1160 }
1161 });
1162 }
1163 }
1164 };
1165
1166 self.onKeyDown = function(e, editorInstance) {
1167 var node = editorInstance.selection.getNode();
1168
1169 if (e.keyCode == 8 || e.keyCode == 46) {
1170 if (node.nodeName.toLowerCase() === 'p') {
1171 var children = $(node).children();
1172 if (children.length > 0) {
1173 $.each(children, function() {
1174 // On delete, make sure to remove the wrapper and children, not only the wrapper
1175 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1176 $(this).remove();
1177
1178 editorInstance.focus();
1179 }
1180 });
1181 }
1182 }
1183 } else {
1184 // Ignore the arrows keys
1185 var arrowsKeyCodes = [37, 38, 39, 40];
1186 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1187
1188 // Check if we are inside a preview wrapper
1189 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1190 // Avoid delete the wrapper or block line break if we are inside the wrapper
1191 if (e.keyCode == 13) {
1192 wrapper = $(self.getWrapperFromChild(node));
1193 if (wrapper.length > 0) {
1194 // Creates a temporary element which will be inserted after the wrapper
1195 var tmpId = '__embedpress__tmp_' + self.makeId();
1196 wrapper.after($('<span id="' + tmpId + '"></span>'));
1197 // Get and select the temporary element
1198 var span = editorInstance.dom.select('span#' + tmpId)[0];
1199 editorInstance.selection.select(span);
1200 // Remove the temporary element
1201 $(span).remove();
1202 }
1203
1204 return true;
1205 } else {
1206 // If we are inside the embed preview, ignore any key to avoid edition
1207 return self.cancelEvent(e, editorInstance);
1208 }
1209 }
1210 }
1211 }
1212
1213 return true;
1214 }
1215
1216 self.getWrapperFromChild = function(element) {
1217 // Is the wrapper
1218 if ($(element).hasClass('embedpress_wrapper')) {
1219 return element;
1220 } else {
1221 var $parent = $(element).parent();
1222
1223 if ($parent.length > 0) {
1224 return self.getWrapperFromChild($parent[0]);
1225 }
1226 }
1227
1228 return false;
1229 };
1230
1231 self.onUndo = function(e, editorInstance) {
1232 // Force re-render everything
1233 editorInstance.load();
1234 };
1235
1236 self.cancelEvent = function(e, editorInstance) {
1237 e.preventDefault();
1238 e.stopPropagation();
1239 editorInstance.dom.events.cancel();
1240
1241 return false;
1242 };
1243
1244 /**
1245 * Method executed when the edit button is clicked. It will display
1246 * a field with the current url, to update the current embed's source
1247 * url.
1248 *
1249 * @param Object e The event
1250 * @return void
1251 */
1252 self.onClickEditButton = function(e, editorInstance) {
1253 // Prevent edition of the panel
1254 self.cancelEvent(e, editorInstance);
1255
1256 self.activeWrapperForModal = self.activeWrapper;
1257
1258 var $wrapper = self.activeWrapperForModal;
1259 var wrapperUid = $wrapper.prop('id').replace("embedpress_wrapper_", "");
1260
1261 var customAttributes = {};
1262
1263 var $embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1264 var embedItem = $('iframe', $wrapper);
1265 if (!embedItem.length) {
1266 embedItem = null;
1267 }
1268
1269 $.each($embedInnerWrapper[0].attributes, function() {
1270 if (this.specified) {
1271 if (this.name !== "class") {
1272 customAttributes[this.name.replace('data-', "").toLowerCase()] = this.value;
1273 }
1274 }
1275 });
1276
1277 var embedWidth = (((embedItem && embedItem.width()) || $embedInnerWrapper.data('width')) || $embedInnerWrapper.width()) || "";
1278 var embedHeight = (((embedItem && embedItem.height()) || $embedInnerWrapper.data('height')) || $embedInnerWrapper.height()) || "";
1279
1280 embedItem = $embedInnerWrapper = null;
1281
1282 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1283
1284 setTimeout(function() {
1285 $.ajax({
1286 type: "GET",
1287 url: self.params.baseUrl +"wp-admin/admin-ajax.php",
1288 data: {
1289 action: "embedpress_get_embed_url_info",
1290 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1291 },
1292 beforeSend: function(request, requestSettings) {
1293 $('.loader-indicator', $wrapper).addClass('is-loading');
1294 },
1295 success: function(response) {
1296 if (!response) {
1297 bootbox.alert('Unable to get a valid response from the server.');
1298 return;
1299 }
1300 if (response.canBeResponsive) {
1301 var embedShouldBeResponsive = true;
1302 if ("width" in customAttributes || "height" in customAttributes) {
1303 embedShouldBeResponsive = false;
1304 } else if ("responsive" in customAttributes && customAttributes['responsive'].isFalse()) {
1305 embedShouldBeResponsive = false;
1306 }
1307 }
1308
1309 bootbox.dialog({
1310 className: "embedpress-modal",
1311 title: "Editing Embed properties",
1312 message: '<form id="form-'+ wrapperUid +'" embedpress>'+
1313 '<div class="row">'+
1314 '<div class="col-md-12">'+
1315 '<div class="form-group">'+
1316 '<label for="input-url-'+ wrapperUid +'">Url</label>'+
1317 '<input class="form-control" type="url" id="input-url-'+ wrapperUid +'" value="'+ self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) +'">'+
1318 '</div>'+
1319 '</div>'+
1320 '</div>'+
1321 '<div class="row">'+
1322 (response.canBeResponsive ?
1323 '<div class="col-md-12">'+
1324 '<label>Responsive</label>'+
1325 '<div class="form-group">'+
1326 '<label class="radio-inline">'+
1327 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-1-'+ wrapperUid +'" value="1"'+ (embedShouldBeResponsive ? ' checked="checked"' : '') +'> Yes'+
1328 '</label>'+
1329 '<label class="radio-inline">'+
1330 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-0-'+ wrapperUid +'" value="0"'+ (!embedShouldBeResponsive ? ' checked="checked"' : '') +'> No'+
1331 '</label>'+
1332 '</div>'+
1333 '</div>' : '')+
1334 '<div class="col-md-6">'+
1335 '<div class="form-group">'+
1336 '<label for="input-width-'+ wrapperUid +'">Width</label>'+
1337 '<input class="form-control" type="integer" id="input-width-'+ wrapperUid +'" value="'+ embedWidth +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1338 '</div>'+
1339 '</div>'+
1340 '<div class="col-md-6">'+
1341 '<div class="form-group">'+
1342 '<label for="input-height-'+ wrapperUid +'">Height</label>'+
1343 '<input class="form-control" type="integer" id="input-height-'+ wrapperUid +'" value="'+ embedHeight +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1344 '</div>'+
1345 '</div>'+
1346 '</div>'+
1347 '</form>',
1348 buttons: {
1349 danger: {
1350 label: "Cancel",
1351 className: "btn-default",
1352 callback: function() {
1353 // do nothing
1354 self.activeWrapperForModal = null;
1355 }
1356 },
1357 success: {
1358 label: "Save",
1359 className: "btn-primary",
1360 callback: function() {
1361 var $wrapper = self.activeWrapperForModal;
1362
1363 // Select the current wrapper as a base for the new element
1364 editorInstance.focus();
1365 editorInstance.selection.select($wrapper[0]);
1366
1367 $wrapper.children().remove();
1368 $wrapper.remove();
1369
1370 if (response.canBeResponsive) {
1371 if ($('#form-'+ wrapperUid +' input[name="input-responsive-'+ wrapperUid +'"]:checked').val().isFalse()) {
1372 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1373 if (parseInt(embedCustomWidth) > 0) {
1374 customAttributes['width'] = embedCustomWidth;
1375 }
1376
1377 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1378 if (parseInt(embedCustomHeight) > 0) {
1379 customAttributes['height'] = embedCustomHeight;
1380 }
1381
1382 customAttributes['responsive'] = "false";
1383 } else {
1384 delete customAttributes['width'];
1385 delete customAttributes['height'];
1386
1387 customAttributes['responsive'] = "true";
1388 }
1389 } else {
1390 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1391 if (parseInt(embedCustomWidth) > 0) {
1392 customAttributes['width'] = embedCustomWidth;
1393 }
1394
1395 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1396 if (parseInt(embedCustomHeight) > 0) {
1397 customAttributes['height'] = embedCustomHeight;
1398 }
1399 }
1400
1401 var customAttributesList = [];
1402 if (!!Object.keys(customAttributes).length) {
1403 for (var attrName in customAttributes) {
1404 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1405 }
1406 }
1407
1408 var shortcode = '['+ $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? " "+ customAttributesList.join(" ") : "") +']'+ $('#input-url-'+ wrapperUid).val() +'[/'+ $data.EMBEDPRESS_SHORTCODE +']';
1409 // We do not directly replace the node because it was causing a bug on a second edit attempt
1410 editorInstance.execCommand('mceInsertContent', false, shortcode);
1411
1412 self.configureWrappers(editorInstance);
1413 }
1414 }
1415 }
1416 });
1417
1418 $('form[embedpress]').on('change', 'input[type="radio"]', function(e) {
1419 var self = $(this);
1420 var form = self.parents('form[embedpress]');
1421
1422 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
1423 });
1424 },
1425 complete: function(request, textStatus) {
1426 $('.loader-indicator', $wrapper).removeClass('is-loading');
1427
1428 setTimeout(function() {
1429 $('.loader-indicator', $wrapper).remove();
1430 }, 350);
1431 },
1432 dataType: "json",
1433 async: true
1434 });
1435 }, 200);
1436
1437 return false;
1438 };
1439
1440 /**
1441 * Method executed when the remove button is clicked. It will remove
1442 * the preview and embed code, adding a mark to ignore the url
1443 *
1444 * @param Object e The event
1445 * @return void
1446 */
1447 self.onClickRemoveButton = function(e, editorInstance) {
1448 // Prevent edition of the panel
1449 self.cancelEvent(e, editorInstance);
1450
1451 var $wrapper = self.activeWrapper;
1452
1453 $wrapper.children().remove();
1454 $wrapper.remove();
1455
1456 return false;
1457 };
1458
1459 self.recursivelyAddClass = function(element, className) {
1460 $(element).children().each(function(index, child) {
1461 $(child).addClass(className);
1462
1463 var grandChild = $(child).children();
1464 if (grandChild.length > 0) {
1465 self.recursivelyAddClass(child, className)
1466 }
1467 });
1468 };
1469
1470 self.setInterval = function(callback, time, timeout) {
1471 var elapsed = 0;
1472 var iteraction = 0;
1473
1474 var interval = window.setInterval(function() {
1475 elapsed += time;
1476 iteraction++;
1477
1478 if (elapsed <= timeout) {
1479 callback(iteraction, elapsed);
1480 } else {
1481 self.stopInterval(interval);
1482 }
1483 }, time);
1484
1485 return interval;
1486 };
1487
1488 self.stopInterval = function(interval) {
1489 window.clearInterval(interval);
1490 interval = null;
1491 };
1492
1493 /**
1494 * Configure unconfigured embed wrappers, adding events and css
1495 * @return void
1496 */
1497 self.configureWrappers = function(editorInstance) {
1498 window.setTimeout(
1499 function configureWrappersTimeOut() {
1500 var doc = editorInstance.getDoc(),
1501 total = 0,
1502 $wrapper = null,
1503 $iframe = null;
1504
1505 // Get all the wrappers
1506 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1507 total = wrappers.length;
1508 if (total > 0) {
1509 for (var i = 0; i < total; i++) {
1510 $wrapper = $(wrappers[i]);
1511
1512 // Check if the wrapper wasn't already configured
1513 if ($wrapper.data('configured') != true) {
1514 // A timeout was set to avoid block the content loading
1515 window.setTimeout(function() {
1516 // @todo: Check if we need a limit of levels to avoid use too much resources
1517 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1518 }, 500);
1519
1520 // Fix the wrapper size. Wait until find the child iframe. L
1521 var interval = self.setInterval(function(iteraction) {
1522 var $childIframes = $wrapper.find('iframe');
1523
1524 if ($childIframes.length > 0) {
1525 $.each($childIframes, function(index, iframe) {
1526 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1527 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1528 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1529 ) {
1530 $wrapper.css('width', $(iframe).width() + 'px');
1531 self.stopInterval(interval);
1532 }
1533 });
1534 }
1535 }, 500, 8000);
1536
1537 $wrapper.data('configured', true);
1538 }
1539 }
1540 }
1541 },
1542 200
1543 );
1544 };
1545
1546 /**
1547 * Hide the controller panel
1548 *
1549 * @return void
1550 */
1551 self.hidePreviewControllerPanel = function() {
1552 if (self.controllerPanelIsActive()) {
1553 $(self.activeControllerPanel).addClass('hidden');
1554 self.activeControllerPanel = null;
1555 self.activeWrapper = null;
1556 }
1557 };
1558
1559 /**
1560 * Get an element by id in the editor's content
1561 *
1562 * @param String id The element id
1563 * @return Element The found element or null, wrapped by jQuery
1564 */
1565 self.getElementInContentById = function(id, editorInstance) {
1566 var doc = editorInstance.getDoc();
1567
1568 return $(doc.getElementById(id));
1569 };
1570
1571 /**
1572 * Show the controller panel
1573 *
1574 * @param element $wrapper The wrapper which will be activate
1575 * @return void
1576 */
1577 self.displayPreviewControllerPanel = function($wrapper, editorInstance) {
1578 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1579 self.hidePreviewControllerPanel();
1580 }
1581
1582 if (!self.controllerPanelIsActive() && !$wrapper.hasClass('is-loading')) {
1583 var uid = $wrapper.data('uid');
1584 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid, editorInstance);
1585
1586 if (!$panel.data('event-set')) {
1587 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid, editorInstance);
1588 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid, editorInstance);
1589
1590 self.addEvent('mousedown', $editButton, function(e) {
1591 self.onClickEditButton(e, editorInstance);
1592 });
1593
1594 self.addEvent('mousedown', $removeButton, function(e) {
1595 self.onClickRemoveButton(e, editorInstance);
1596 });
1597
1598 $panel.data('event-set', true);
1599 }
1600
1601 // Update the position of the control bar
1602 var next = $panel.next()[0];
1603 if (typeof next !== 'undefined') {
1604 if (next.nodeName.toLowerCase() === 'iframe') {
1605 $panel.css('left', ($(next).width() / 2));
1606 }
1607 }
1608
1609 // Show the bar
1610 $panel.removeClass('hidden');
1611
1612 self.activeControllerPanel = $panel;
1613 self.activeWrapper = $wrapper;
1614 }
1615 };
1616 };
1617
1618 if (!window.EmbedPress) {
1619 window.EmbedPress = new EmbedPress();
1620 }
1621
1622 window.EmbedPress.init($data.previewSettings);
1623 });
1624 })(jQuery, String, $data);
1625