PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 2.1.4
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v2.1.4
4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 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
1624 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 $wrapper.removeClass('embedpress_placeholder');
567
568 $wrapper.append(contentWrapper);
569
570 setTimeout(function() {
571 editorInstance.undoManager.transact(function() {
572 var iframe = editorInstance.getDoc().createElement('iframe');
573 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
574 iframe.frameBorder = '0';
575 iframe.allowTransparency = 'true';
576 iframe.scrolling = 'no';
577 iframe.class = "wpview-sandbox";
578 iframe.style.width = '100%';
579
580 contentWrapper.append(iframe);
581
582 var iframeWindow = iframe.contentWindow;
583 // Content failed to load.
584 if (!iframeWindow) {
585 return;
586 }
587
588 var iframeDoc = iframeWindow.document;
589
590 $(iframe).load(function() {
591 var maximumChecksAllowed = 8;
592 var checkIndex = 0;
593
594 var checkerInterval = setInterval(function() {
595 if (checkIndex === maximumChecksAllowed) {
596 clearInterval(checkerInterval);
597
598 setTimeout(function() {
599 $wrapper.css('width', iframe.width);
600 $wrapper.css('height', iframe.height);
601 }, 100);
602 } else {
603 if (customAttributes.height) {
604 iframe.height = customAttributes.height;
605 iframe.style.height = customAttributes.height +'px';
606 } else {
607 iframe.height = $('body', iframeDoc).height();
608 }
609
610 if (customAttributes.width) {
611 iframe.width = customAttributes.width;
612 iframe.style.width = customAttributes.width +'px';
613 } else {
614 iframe.width = $('body', iframeDoc).width();
615 }
616
617 checkIndex++;
618 }
619 }, 250);
620 });
621
622 iframeDoc.open();
623 iframeDoc.write(
624 '<!DOCTYPE html>'+
625 '<html>'+
626 '<head>'+
627 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'+
628 '<style>'+
629 'html {'+
630 'background: transparent;'+
631 'padding: 0;'+
632 'margin: 0;'+
633 '}'+
634 'body#wpview-iframe-sandbox {'+
635 'background: transparent;'+
636 'padding: 1px 0 !important;'+
637 'margin: -1px 0 0 !important;'+
638 '}'+
639 'body#wpview-iframe-sandbox:before,'+
640 'body#wpview-iframe-sandbox:after {'+
641 'display: none;'+
642 'content: "";'+
643 '}'+
644 '</style>'+
645 '</head>'+
646 '<body id="wpview-iframe-sandbox" class="'+ editorInstance.getBody().className +'" style="display: inline-block;">'+
647 $content.html() +
648 '</body>'+
649 '</html>'
650 );
651 iframeDoc.close();
652 });
653 }, 50);
654 } else {
655 $wrapper.removeClass('embedpress_placeholder');
656
657 self.appendElementsIntoWrapper($content, $wrapper, editorInstance);
658 }
659
660 $wrapper.append($('<span class="wpview-end"></span>'));
661
662 if (result && result.data && typeof result.data === "object") {
663 result.data.width = $($wrapper).width();
664 result.data.height = $($wrapper).height();
665 }
666
667 $(self).triggerHandler('EmbedPress.afterEmbed', {
668 'meta' : result.data,
669 'url' : rawUrl,
670 'wrapper': $wrapper
671 });
672 });
673 };
674
675 self.appendElementsIntoWrapper = function(elementsList, wrapper, editorInstance) {
676 if (elementsList.length > 0) {
677 $.each(elementsList, function appendElementIntoWrapper(elementIndex, element) {
678 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
679 if (element.tagName.toLowerCase() !== 'script') {
680 wrapper.append($(element));
681
682 if (element.tagName.toLowerCase() === 'iframe') {
683 $(element).ready(function() {
684 window.setTimeout(function() {
685 $.each(editorInstance.dom.select('div.embedpress_wrapper iframe'), function(elementIndex, iframe) {
686 self.fixIframeSize(iframe);
687 });
688 }, 300);
689 });
690 } else if (element.tagName.toLowerCase() === "div") {
691 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
692 // This ensures that the embed wrapper have the same width as its content
693 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
694 }
695
696 $(element).css('max-width', $($(element).parents('body').get(0)).width());
697 }
698 }
699
700 self.loadAsyncDynamicJsCodeFromElement(element, wrapper, editorInstance);
701 });
702 }
703
704 return wrapper;
705 };
706
707 self.encodeEmbedURLSpecialChars = function(content) {
708 if (content.match(SHORTCODE_REGEXP)) {
709 var subject = content.replace(SHORTCODE_REGEXP, '');
710
711 if (!subject.isValidUrl()) {
712 return content;
713 }
714
715 content = subject;
716 subject = null;
717 }
718
719 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
720 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
721
722 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
723 content = content.replace('@', '::__at__::').trim();
724
725 return content;
726 };
727
728 self.decodeEmbedURLSpecialChars = function(content, applyShortcode, attributes) {
729 var encodingRegexpRule = /embedpress(s?):\/\//;
730 applyShortcode = (typeof applyShortcode === "undefined") ? true : applyShortcode;
731 attributes = (typeof attributes === "undefined") ? {} : attributes;
732
733 var isEncoded = content.match(encodingRegexpRule);
734
735 // Restore http[s] in the url (converted to bypass autolink plugin)
736 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
737 content = content.replace('::__at__::', '@').trim();
738
739 if ("class" in attributes) {
740 var classesList = attributes.class.split(/\s/g);
741 var shouldRemoveDynamicWidthClass = false;
742 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
743 if (classesList[classIndex] === "dynamic-width") {
744 shouldRemoveDynamicWidthClass = classIndex;
745 break;
746 }
747 }
748
749 if (shouldRemoveDynamicWidthClass !== false) {
750 classesList.splice(shouldRemoveDynamicWidthClass, 1);
751
752 if (classesList.length === 0) {
753 delete attributes.class;
754 }
755
756 attributes.class = classesList.join(" ");
757 }
758
759 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
760 }
761
762 if (isEncoded && applyShortcode) {
763 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE;
764 if (!!Object.keys(attributes).length) {
765 var attrValue;
766
767 for (var attrName in attributes) {
768 attrValue = attributes[attrName];
769
770 // Prevent `class` property being empty an treated as a boolean param
771 if (attrName.toLowerCase() === "class" && !attrValue.length) {
772 continue;
773 }
774 else {
775 if (attrValue.isBoolean()) {
776 shortcode += " ";
777 if (attrValue.isFalse()) {
778 shortcode += "!";
779 }
780
781 shortcode += attrName
782 } else {
783 shortcode += ' '+ attrName +'="'+ attrValue +'"';
784 }
785 }
786 }
787 attrValue = attrName = null;
788 }
789
790 content = shortcode + ']' + content + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
791 }
792
793 return content;
794 };
795
796 /**
797 * Method executed after find the editor. It will make additional
798 * configurations and add the content's stylesheets for the preview
799 *
800 * @return void
801 */
802 self.onFindEditor = function(editorInstance) {
803 self.each = tinymce.each;
804 self.extend = tinymce.extend;
805 self.JSON = tinymce.util.JSON;
806 self.Node = tinymce.html.Node;
807
808 function onFindEditorCallback() {
809 $(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) + '">'));
810
811 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID, editorInstance, editorInstance);
812 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID, editorInstance, editorInstance);
813 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID, editorInstance, editorInstance);
814 self.addEvent('nodechange', editorInstance, self.onNodeChange);
815 self.addEvent('keydown', editorInstance, function(e) {
816 self.onKeyDown(e, editorInstance);
817 });
818
819 var onUndoCallback = function(e) {
820 self.onUndo(e, editorInstance);
821 };
822
823 self.addEvent('undo', editorInstance, onUndoCallback); // TinyMCE
824 self.addEvent('undo', editorInstance.undoManager, onUndoCallback); // JCE
825
826 var doc = editorInstance.getDoc();
827 $(doc).on('mouseenter', '.embedpress_wrapper', function(e) {
828 self.onMouseEnter(e, editorInstance);
829 });
830 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
831 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', function(e) {
832 self.cancelEvent(e, editorInstance)
833 });
834 doc = null;
835
836 // Add the node filter that will convert the url into the preview box for the embed code
837 editorInstance.parser.addNodeFilter('#text', function addNodeFilterIntoParser(nodes, arg) {
838 self.each(nodes, function eachNodeInParser(node) {
839 var subject = node.value.trim();
840
841
842 if (!subject.isValidUrl()) {
843 if (!subject.match(SHORTCODE_REGEXP)) {
844 return;
845 }
846 }
847 subject = self.decodeEmbedURLSpecialChars(subject);
848 if (!subject.isValidUrl()) {
849 if (!subject.match(SHORTCODE_REGEXP)) {
850 return;
851 }
852 }
853
854
855 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
856
857 // These patterns need to have groups for the pre and post texts
858 // @TODO: maybe remove this list of URLs? Let the server side code decide what URL should be parsed
859 var patterns = self.getProvidersURLPatterns();
860
861 (function tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex) {
862 if (urlPatternIndex < patterns.length) {
863 var urlPattern = patterns[urlPatternIndex];
864 var urlPatternRegex = new RegExp(urlPattern);
865
866 var url = self.decodeEmbedURLSpecialChars(subject).trim();
867
868 var matches = url.match(urlPatternRegex);
869 // Check if content matches the url pattern.
870 if (matches && matches !== null && !!matches.length) {
871 url = self.encodeEmbedURLSpecialChars(matches[2]);
872
873 var wrapper = self.addURLsPlaceholder(node, url, editorInstance);
874
875 setTimeout(function() {
876 var doc = editorInstance.getDoc();
877
878 var previewWrapper = $(doc.querySelector('#'+ wrapper.attributes.map['id']));
879 var previewWrapperParent = $(previewWrapper.parent());
880
881 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === "P") {
882 previewWrapperParent.replaceWith(previewWrapper);
883 }
884
885 var previewWrapperOlderSibling = previewWrapper.prev();
886 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === "P" && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
887 previewWrapperOlderSibling.remove();
888 } else {
889 if (typeof previewWrapperOlderSibling.html() !== 'undefined') {
890 if (previewWrapperOlderSibling.html().match(/<[\/]?br>/)) {
891 if (!previewWrapperOlderSibling.prev().length) {
892 previewWrapperOlderSibling.remove();
893 }
894 }
895 }
896 }
897
898 var previewWrapperYoungerSibling = previewWrapper.next();
899 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === "P") {
900 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
901 previewWrapperYoungerSibling.remove();
902 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
903 }
904 } else {
905 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
906 }
907
908 setTimeout(function() {
909 editorInstance.selection.select(editorInstance.getBody(), true);
910 editorInstance.selection.collapse(false);
911 }, 50);
912 }, 50);
913 } else {
914 // No match. So we move on to check the next url pattern.
915 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
916 }
917 }
918 })(0);
919 });
920 });
921
922 // Add the filter that will convert the preview box/embed code back to the raw url
923 editorInstance.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer(nodes, arg) {
924 self.each(nodes, function eachNodeInSerializer(node) {
925 var nodeClasses = (node.attributes.map.class || "").split(' ');
926 var wrapperFactoryClasses = ["embedpress_wrapper", "embedpress_placeholder", "wpview", "wpview-wrap"];
927
928 var isWrapped = nodeClasses.filter(function(n) {
929 return wrapperFactoryClasses.indexOf(n) != -1;
930 }).length > 0;
931
932 if (isWrapped) {
933 var factoryAttributes = ["id", "style", "data-loading-text", "data-uid", "data-url"];
934 var customAttributes = {};
935 var dataPrefix = "data-";
936 for (var attr in node.attributes.map) {
937 if (attr.toLowerCase() !== "class") {
938 if (factoryAttributes.indexOf(attr) < 0) {
939 // Remove the "data-" prefix for more readability
940 customAttributes[attr.replace(dataPrefix, "")] = node.attributes.map[attr];
941 }
942 } else {
943 var customClasses = [];
944 for (var wrapperClassIndex in nodeClasses) {
945 var wrapperClass = nodeClasses[wrapperClassIndex];
946 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
947 customClasses.push(wrapperClass);
948 }
949 }
950
951 if (!!customClasses.length) {
952 customAttributes.class = customClasses.join(" ");
953 }
954 }
955 }
956
957 var p = new self.Node('p', 1);
958
959 var text = new self.Node('#text', 3);
960 text.value = self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes);
961
962 p.append(text.clone());
963
964 node.replace(text);
965 text.replace(p);
966 }
967 });
968 });
969
970 editorInstance.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer(nodes, arg) {
971 self.each(nodes, function eachNodeInSerializer(node) {
972 if (node.firstChild == node.lastChild) {
973 if (node.firstChild && "value" in node.firstChild && (node.firstChild.value === "&nbsp;" || !node.firstChild.value.trim().length)) {
974 node.remove();
975 }
976 }
977 });
978 });
979
980 //@todo:isthiseachreallynecessary?
981 // Add event to reconfigure wrappers every time the content is loaded
982 tinymce.each(tinymce.editors, function onEachEditor(editor) {
983 self.addEvent('loadContent', editor, function onInitEditor(ed) {
984 self.configureWrappers(editor);
985 });
986 });
987
988 // Add the edit form
989
990 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
991 window.setTimeout(
992 function afterTimeoutOnFindEditor() {
993 /*
994 * This is required because after load/refresh the page, the
995 * onLoadContent is not being triggered automatically, so
996 * we force the event
997 */
998 editorInstance.load();
999 },
1000 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1001 500
1002 );
1003 }
1004
1005 // Let's make sure the inner doc has been fully loaded first.
1006 var checkTimesLimit = 100;
1007 var checkIndex = 0;
1008 var statusCheckerInterval = setInterval(function() {
1009 if (checkIndex === checkTimesLimit) {
1010 clearInterval(statusCheckerInterval);
1011 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1012 } else {
1013 var doc = editorInstance.getDoc();
1014 if (doc) {
1015 clearInterval(statusCheckerInterval);
1016 onFindEditorCallback();
1017 } else {
1018 checkIndex++;
1019 }
1020 }
1021 }, 250);
1022 };
1023
1024 self.fixIframeSize = function(iframe) {
1025 var maxWidth = 480;
1026 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1027 var ratio = $(iframe).height() / $(iframe).width();
1028 $(iframe).width(maxWidth);
1029 $(iframe).height(maxWidth * ratio);
1030 $(iframe).css('max-width', maxWidth);
1031 $(iframe).attr('max-width', maxWidth);
1032
1033 $(iframe).data('size-fixed', true);
1034 }
1035 }
1036
1037 /**
1038 * Function triggered on mouse enter the wrapper
1039 *
1040 * @param object e The event
1041 * @return void
1042 */
1043 self.onMouseEnter = function(e, editorInstance) {
1044 self.displayPreviewControllerPanel($(e.currentTarget), editorInstance);
1045 };
1046
1047 /**
1048 * Function triggered on mouse get out of the wrapper
1049 *
1050 * @param object e The event
1051 * @return void
1052 */
1053 self.onMouseOut = function(e) {
1054 // Check if the destiny is not a child element
1055 // Chrome
1056 if (self.isDefined(e.toElement)) {
1057 if (e.toElement.parentElement == e.fromElement
1058 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1059 ) {
1060 return false;
1061 }
1062 }
1063
1064 // Firefox
1065 if (self.isDefined(e.relatedTarget)) {
1066 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1067 return false;
1068 }
1069 }
1070
1071 self.hidePreviewControllerPanel();
1072 };
1073
1074 /**
1075 * Callback triggered by paste events. This should be hooked by TinyMCE's paste_preprocess
1076 * setting. A normal bind to the onPaste event doesn't work correctly all the times
1077 * (specially when you copy and paste content from the same editor).
1078 *
1079 * @param mixed - plugin
1080 * @param mixed - args
1081 *
1082 * @return void
1083 */
1084
1085 self.onPaste = function(plugin, args) {
1086 var urlPatternRegex = new RegExp(/(https?):\/\/([w]{3}\.)?.+?(?:\s|$)/i);
1087 var urlPatternsList = self.getProvidersURLPatterns();
1088
1089 // Split the pasted content into separated lines.
1090 var contentLines = args.content.split(/\n/g) || [];
1091 contentLines = contentLines.map(function(line, itemIndex) {
1092 // Check if there's a url into `line`.
1093 if (line.match(urlPatternRegex)) {
1094 // Split the current line across its space-characters to isolate the url.
1095 let termsList = line.trim().split(/\s+/);
1096 termsList = termsList.map(function(term, termIndex) {
1097 // Check if the term into the current line is a url.
1098 var match = term.match(urlPatternRegex);
1099 if (match) {
1100 for (var urlPatternIndex = 0; urlPatternIndex < urlPatternsList.length; urlPatternIndex++) {
1101 // Isolates that url from the rest of the content if the service is supported.
1102 var urlPattern = new RegExp(urlPatternsList[urlPatternIndex]);
1103 if (urlPattern.test(term)) {
1104 return '</p><p>'+ match[0] +'</p><p>';
1105 }
1106 }
1107 }
1108
1109 return term;
1110 });
1111
1112 termsList[termsList.length - 1] = termsList[termsList.length - 1] + '<br>';
1113
1114 line = termsList.join(' ');
1115 }
1116
1117 return line;
1118 });
1119
1120 // Check if the text was transformed or not. If it was, add wrappers
1121 var content = contentLines.join('');
1122
1123 if (content.replace(/<br>$/, '') !== args.content) {
1124 args.content = '<p>'+ args.content +'</p>';
1125 }
1126 };
1127
1128 /**
1129 * Method trigered on every node change, to detect new lines. It will
1130 * try to fix a default behavior for some editors of clone the parent
1131 * element when adding a line break. This will clone the embed wrapper
1132 * if we set the cursor after a preview wrapper and hit enter.
1133 *
1134 * @param object e The event
1135 * @return void
1136 */
1137 self.onNodeChange = function(e) {
1138 // Fix the clone parent on break lines issue
1139 // Check if a line break was added
1140 if (e.element.tagName === 'BR') {
1141 // Check one of the parent elements is a clonned embed wrapper
1142 if (e.parents.length > 0) {
1143 $.each(e.parents, function(index, parent) {
1144 if ($(parent).hasClass('embedpress_wrapper')) {
1145 // Remove the cloned wrapper and replace with a 'br' tag
1146 $(parent).replaceWith($('<br>'));
1147 }
1148 });
1149 }
1150 } else if (e.element.tagName === "IFRAME") {
1151 if (e.parents.length > 0) {
1152 $.each(e.parents, function(index, parent) {
1153 parent = $(parent);
1154 if (parent.hasClass('embedpress_wrapper')) {
1155 var wrapper = $('.embedpress-wrapper', parent);
1156 if (wrapper.length > 1) {
1157 wrapper.get(0).remove();
1158 }
1159 }
1160 });
1161 }
1162 }
1163 };
1164
1165 self.onKeyDown = function(e, editorInstance) {
1166 var node = editorInstance.selection.getNode();
1167
1168 if (e.keyCode == 8 || e.keyCode == 46) {
1169 if (node.nodeName.toLowerCase() === 'p') {
1170 var children = $(node).children();
1171 if (children.length > 0) {
1172 $.each(children, function() {
1173 // On delete, make sure to remove the wrapper and children, not only the wrapper
1174 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1175 $(this).remove();
1176
1177 editorInstance.focus();
1178 }
1179 });
1180 }
1181 }
1182 } else {
1183 // Ignore the arrows keys
1184 var arrowsKeyCodes = [37, 38, 39, 40];
1185 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1186
1187 // Check if we are inside a preview wrapper
1188 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1189 // Avoid delete the wrapper or block line break if we are inside the wrapper
1190 if (e.keyCode == 13) {
1191 wrapper = $(self.getWrapperFromChild(node));
1192 if (wrapper.length > 0) {
1193 // Creates a temporary element which will be inserted after the wrapper
1194 var tmpId = '__embedpress__tmp_' + self.makeId();
1195 wrapper.after($('<span id="' + tmpId + '"></span>'));
1196 // Get and select the temporary element
1197 var span = editorInstance.dom.select('span#' + tmpId)[0];
1198 editorInstance.selection.select(span);
1199 // Remove the temporary element
1200 $(span).remove();
1201 }
1202
1203 return true;
1204 } else {
1205 // If we are inside the embed preview, ignore any key to avoid edition
1206 return self.cancelEvent(e, editorInstance);
1207 }
1208 }
1209 }
1210 }
1211
1212 return true;
1213 }
1214
1215 self.getWrapperFromChild = function(element) {
1216 // Is the wrapper
1217 if ($(element).hasClass('embedpress_wrapper')) {
1218 return element;
1219 } else {
1220 var $parent = $(element).parent();
1221
1222 if ($parent.length > 0) {
1223 return self.getWrapperFromChild($parent[0]);
1224 }
1225 }
1226
1227 return false;
1228 };
1229
1230 self.onUndo = function(e, editorInstance) {
1231 // Force re-render everything
1232 editorInstance.load();
1233 };
1234
1235 self.cancelEvent = function(e, editorInstance) {
1236 e.preventDefault();
1237 e.stopPropagation();
1238 editorInstance.dom.events.cancel();
1239
1240 return false;
1241 };
1242
1243 /**
1244 * Method executed when the edit button is clicked. It will display
1245 * a field with the current url, to update the current embed's source
1246 * url.
1247 *
1248 * @param Object e The event
1249 * @return void
1250 */
1251 self.onClickEditButton = function(e, editorInstance) {
1252 // Prevent edition of the panel
1253 self.cancelEvent(e, editorInstance);
1254
1255 self.activeWrapperForModal = self.activeWrapper;
1256
1257 var $wrapper = self.activeWrapperForModal;
1258 var wrapperUid = $wrapper.prop('id').replace("embedpress_wrapper_", "");
1259
1260 var customAttributes = {};
1261
1262 var $embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1263 var embedItem = $('iframe', $wrapper);
1264 if (!embedItem.length) {
1265 embedItem = null;
1266 }
1267
1268 $.each($embedInnerWrapper[0].attributes, function() {
1269 if (this.specified) {
1270 if (this.name !== "class") {
1271 customAttributes[this.name.replace('data-', "").toLowerCase()] = this.value;
1272 }
1273 }
1274 });
1275
1276 var embedWidth = (((embedItem && embedItem.width()) || $embedInnerWrapper.data('width')) || $embedInnerWrapper.width()) || "";
1277 var embedHeight = (((embedItem && embedItem.height()) || $embedInnerWrapper.data('height')) || $embedInnerWrapper.height()) || "";
1278
1279 embedItem = $embedInnerWrapper = null;
1280
1281 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1282
1283 setTimeout(function() {
1284 $.ajax({
1285 type: "GET",
1286 url: self.params.baseUrl +"wp-admin/admin-ajax.php",
1287 data: {
1288 action: "embedpress_get_embed_url_info",
1289 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1290 },
1291 beforeSend: function(request, requestSettings) {
1292 $('.loader-indicator', $wrapper).addClass('is-loading');
1293 },
1294 success: function(response) {
1295 if (!response) {
1296 bootbox.alert('Unable to get a valid response from the server.');
1297 return;
1298 }
1299 if (response.canBeResponsive) {
1300 var embedShouldBeResponsive = true;
1301 if ("width" in customAttributes || "height" in customAttributes) {
1302 embedShouldBeResponsive = false;
1303 } else if ("responsive" in customAttributes && customAttributes['responsive'].isFalse()) {
1304 embedShouldBeResponsive = false;
1305 }
1306 }
1307
1308 bootbox.dialog({
1309 className: "embedpress-modal",
1310 title: "Editing Embed properties",
1311 message: '<form id="form-'+ wrapperUid +'" embedpress>'+
1312 '<div class="row">'+
1313 '<div class="col-md-12">'+
1314 '<div class="form-group">'+
1315 '<label for="input-url-'+ wrapperUid +'">Url</label>'+
1316 '<input class="form-control" type="url" id="input-url-'+ wrapperUid +'" value="'+ self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) +'">'+
1317 '</div>'+
1318 '</div>'+
1319 '</div>'+
1320 '<div class="row">'+
1321 (response.canBeResponsive ?
1322 '<div class="col-md-12">'+
1323 '<label>Responsive</label>'+
1324 '<div class="form-group">'+
1325 '<label class="radio-inline">'+
1326 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-1-'+ wrapperUid +'" value="1"'+ (embedShouldBeResponsive ? ' checked="checked"' : '') +'> Yes'+
1327 '</label>'+
1328 '<label class="radio-inline">'+
1329 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-0-'+ wrapperUid +'" value="0"'+ (!embedShouldBeResponsive ? ' checked="checked"' : '') +'> No'+
1330 '</label>'+
1331 '</div>'+
1332 '</div>' : '')+
1333 '<div class="col-md-6">'+
1334 '<div class="form-group">'+
1335 '<label for="input-width-'+ wrapperUid +'">Width</label>'+
1336 '<input class="form-control" type="integer" id="input-width-'+ wrapperUid +'" value="'+ embedWidth +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1337 '</div>'+
1338 '</div>'+
1339 '<div class="col-md-6">'+
1340 '<div class="form-group">'+
1341 '<label for="input-height-'+ wrapperUid +'">Height</label>'+
1342 '<input class="form-control" type="integer" id="input-height-'+ wrapperUid +'" value="'+ embedHeight +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1343 '</div>'+
1344 '</div>'+
1345 '</div>'+
1346 '</form>',
1347 buttons: {
1348 danger: {
1349 label: "Cancel",
1350 className: "btn-default",
1351 callback: function() {
1352 // do nothing
1353 self.activeWrapperForModal = null;
1354 }
1355 },
1356 success: {
1357 label: "Save",
1358 className: "btn-primary",
1359 callback: function() {
1360 var $wrapper = self.activeWrapperForModal;
1361
1362 // Select the current wrapper as a base for the new element
1363 editorInstance.focus();
1364 editorInstance.selection.select($wrapper[0]);
1365
1366 $wrapper.children().remove();
1367 $wrapper.remove();
1368
1369 if (response.canBeResponsive) {
1370 if ($('#form-'+ wrapperUid +' input[name="input-responsive-'+ wrapperUid +'"]:checked').val().isFalse()) {
1371 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1372 if (parseInt(embedCustomWidth) > 0) {
1373 customAttributes['width'] = embedCustomWidth;
1374 }
1375
1376 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1377 if (parseInt(embedCustomHeight) > 0) {
1378 customAttributes['height'] = embedCustomHeight;
1379 }
1380
1381 customAttributes['responsive'] = "false";
1382 } else {
1383 delete customAttributes['width'];
1384 delete customAttributes['height'];
1385
1386 customAttributes['responsive'] = "true";
1387 }
1388 } else {
1389 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1390 if (parseInt(embedCustomWidth) > 0) {
1391 customAttributes['width'] = embedCustomWidth;
1392 }
1393
1394 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1395 if (parseInt(embedCustomHeight) > 0) {
1396 customAttributes['height'] = embedCustomHeight;
1397 }
1398 }
1399
1400 var customAttributesList = [];
1401 if (!!Object.keys(customAttributes).length) {
1402 for (var attrName in customAttributes) {
1403 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1404 }
1405 }
1406
1407 var shortcode = '['+ $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? " "+ customAttributesList.join(" ") : "") +']'+ $('#input-url-'+ wrapperUid).val() +'[/'+ $data.EMBEDPRESS_SHORTCODE +']';
1408 // We do not directly replace the node because it was causing a bug on a second edit attempt
1409 editorInstance.execCommand('mceInsertContent', false, shortcode);
1410
1411 self.configureWrappers(editorInstance);
1412 }
1413 }
1414 }
1415 });
1416
1417 $('form[embedpress]').on('change', 'input[type="radio"]', function(e) {
1418 var self = $(this);
1419 var form = self.parents('form[embedpress]');
1420
1421 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
1422 });
1423 },
1424 complete: function(request, textStatus) {
1425 $('.loader-indicator', $wrapper).removeClass('is-loading');
1426
1427 setTimeout(function() {
1428 $('.loader-indicator', $wrapper).remove();
1429 }, 350);
1430 },
1431 dataType: "json",
1432 async: true
1433 });
1434 }, 200);
1435
1436 return false;
1437 };
1438
1439 /**
1440 * Method executed when the remove button is clicked. It will remove
1441 * the preview and embed code, adding a mark to ignore the url
1442 *
1443 * @param Object e The event
1444 * @return void
1445 */
1446 self.onClickRemoveButton = function(e, editorInstance) {
1447 // Prevent edition of the panel
1448 self.cancelEvent(e, editorInstance);
1449
1450 var $wrapper = self.activeWrapper;
1451
1452 $wrapper.children().remove();
1453 $wrapper.remove();
1454
1455 return false;
1456 };
1457
1458 self.recursivelyAddClass = function(element, className) {
1459 $(element).children().each(function(index, child) {
1460 $(child).addClass(className);
1461
1462 var grandChild = $(child).children();
1463 if (grandChild.length > 0) {
1464 self.recursivelyAddClass(child, className)
1465 }
1466 });
1467 };
1468
1469 self.setInterval = function(callback, time, timeout) {
1470 var elapsed = 0;
1471 var iteraction = 0;
1472
1473 var interval = window.setInterval(function() {
1474 elapsed += time;
1475 iteraction++;
1476
1477 if (elapsed <= timeout) {
1478 callback(iteraction, elapsed);
1479 } else {
1480 self.stopInterval(interval);
1481 }
1482 }, time);
1483
1484 return interval;
1485 };
1486
1487 self.stopInterval = function(interval) {
1488 window.clearInterval(interval);
1489 interval = null;
1490 };
1491
1492 /**
1493 * Configure unconfigured embed wrappers, adding events and css
1494 * @return void
1495 */
1496 self.configureWrappers = function(editorInstance) {
1497 window.setTimeout(
1498 function configureWrappersTimeOut() {
1499 var doc = editorInstance.getDoc(),
1500 total = 0,
1501 $wrapper = null,
1502 $iframe = null;
1503
1504 // Get all the wrappers
1505 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1506 total = wrappers.length;
1507 if (total > 0) {
1508 for (var i = 0; i < total; i++) {
1509 $wrapper = $(wrappers[i]);
1510
1511 // Check if the wrapper wasn't already configured
1512 if ($wrapper.data('configured') != true) {
1513 // A timeout was set to avoid block the content loading
1514 window.setTimeout(function() {
1515 // @todo: Check if we need a limit of levels to avoid use too much resources
1516 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1517 }, 500);
1518
1519 // Fix the wrapper size. Wait until find the child iframe. L
1520 var interval = self.setInterval(function(iteraction) {
1521 var $childIframes = $wrapper.find('iframe');
1522
1523 if ($childIframes.length > 0) {
1524 $.each($childIframes, function(index, iframe) {
1525 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1526 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1527 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1528 ) {
1529 $wrapper.css('width', $(iframe).width() + 'px');
1530 self.stopInterval(interval);
1531 }
1532 });
1533 }
1534 }, 500, 8000);
1535
1536 $wrapper.data('configured', true);
1537 }
1538 }
1539 }
1540 },
1541 200
1542 );
1543 };
1544
1545 /**
1546 * Hide the controller panel
1547 *
1548 * @return void
1549 */
1550 self.hidePreviewControllerPanel = function() {
1551 if (self.controllerPanelIsActive()) {
1552 $(self.activeControllerPanel).addClass('hidden');
1553 self.activeControllerPanel = null;
1554 self.activeWrapper = null;
1555 }
1556 };
1557
1558 /**
1559 * Get an element by id in the editor's content
1560 *
1561 * @param String id The element id
1562 * @return Element The found element or null, wrapped by jQuery
1563 */
1564 self.getElementInContentById = function(id, editorInstance) {
1565 var doc = editorInstance.getDoc();
1566
1567 return $(doc.getElementById(id));
1568 };
1569
1570 /**
1571 * Show the controller panel
1572 *
1573 * @param element $wrapper The wrapper which will be activate
1574 * @return void
1575 */
1576 self.displayPreviewControllerPanel = function($wrapper, editorInstance) {
1577 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1578 self.hidePreviewControllerPanel();
1579 }
1580
1581 if (!self.controllerPanelIsActive() && !$wrapper.hasClass('is-loading')) {
1582 var uid = $wrapper.data('uid');
1583 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid, editorInstance);
1584
1585 if (!$panel.data('event-set')) {
1586 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid, editorInstance);
1587 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid, editorInstance);
1588
1589 self.addEvent('mousedown', $editButton, function(e) {
1590 self.onClickEditButton(e, editorInstance);
1591 });
1592
1593 self.addEvent('mousedown', $removeButton, function(e) {
1594 self.onClickRemoveButton(e, editorInstance);
1595 });
1596
1597 $panel.data('event-set', true);
1598 }
1599
1600 // Update the position of the control bar
1601 var next = $panel.next()[0];
1602 if (typeof next !== 'undefined') {
1603 if (next.nodeName.toLowerCase() === 'iframe') {
1604 $panel.css('left', ($(next).width() / 2));
1605 }
1606 }
1607
1608 // Show the bar
1609 $panel.removeClass('hidden');
1610
1611 self.activeControllerPanel = $panel;
1612 self.activeWrapper = $wrapper;
1613 }
1614 };
1615 };
1616
1617 if (!window.EmbedPress) {
1618 window.EmbedPress = new EmbedPress();
1619 }
1620
1621 window.EmbedPress.init($data.previewSettings);
1622 });
1623 })(jQuery, String, $data);
1624