PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.4.6
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.4.6
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
chunks 7 months ago vendor 8 months ago admin.build.js 7 months ago admin.js 9 months ago analytics-tracker.js 9 months ago analytics.build.js 7 months ago blocks.build.js 7 months ago carousel.js 1 year ago documents-viewer-script.js 3 years ago feature-notices.js 7 months ago front.js 7 months ago frontend.build.js 9 months ago gallery-justify.js 9 months ago gutneberg-script.js 1 year ago index.html 7 years ago initCarousel.js 2 years ago initplyr.js 8 months ago instafeed.js 7 months ago license.js 7 months ago preview.js 8 months ago settings.js 7 months ago sponsored.js 9 months ago
preview.js
1657 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, embedpressPreviewData, 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 ats in attributes) {
52 parsedAttributes.push(ats + '="' + attributes[ats] + '"');
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('\\[\/?' + embedpressPreviewData.shortcode + '\\]', 'gi');
151
152 var EmbedPress = function () {
153 var self = this;
154
155 var PLG_SYSTEM_ASSETS_CSS_PATH = embedpressPreviewData.assetsUrl + '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 subject = $(subject);
241 if (subject.prop('tagName') && subject.prop('tagName').toLowerCase() === 'script') {
242 var scriptSrc = subject.attr('src') || null;
243 if (!scriptSrc) {
244 self.addScriptDeclaration(wrapper, subject.html(), editorInstance);
245 } else {
246 self.addScript(scriptSrc, null, wrapper, editorInstance);
247 }
248 } else {
249 var innerScriptsList = $('script', subject);
250 if (innerScriptsList.length > 0) {
251 $.each(innerScriptsList, function (innerScriptIndex, innerScript) {
252 self.loadAsyncDynamicJsCodeFromElement(innerScript, wrapper, editorInstance);
253 });
254 }
255 }
256 };
257
258 /**
259 * Method executed on the document ready event
260 *
261 * @return void
262 */
263 self.onReady = function () {
264 var findEditors = function () {
265 // Wait until the editor is available
266 var interval = window.setInterval(
267 function () {
268 var editorsFound = self.getEditors();
269 if (editorsFound.length) {
270 self.loadedEditors = editorsFound;
271
272 for (var editorIndex = 0; editorIndex < self.loadedEditors.length; editorIndex++) {
273 self.onFindEditor(self.loadedEditors[editorIndex]);
274 }
275
276 window.clearInterval(interval);
277
278 return self.loadedEditors;
279 }
280 },
281 250
282 );
283 };
284
285 if (self.tinymceIsAvailable()) {
286 findEditors();
287 }
288
289 // Add support for the Beaver Builder.
290 if (typeof FLLightbox !== 'undefined') {
291 $.each(FLLightbox._instances, function (index) {
292 FLLightbox._instances[index].on('open', function () {
293 setTimeout(function () {
294 findEditors();
295 }, 500);
296 });
297
298 FLLightbox._instances[index].on('didHideLightbox', function () {
299 setTimeout(function () {
300 findEditors();
301 }, 500);
302 });
303 });
304 }
305 };
306
307 /**
308 * Detects if tinymce object is available
309 * @return Boolean True, if available
310 */
311 self.tinymceIsAvailable = function () {
312 return typeof window.tinymce === 'object' || typeof window.tinyMCE === 'object';
313 };
314
315 /**
316 * Returns true if the controller panel is active
317 * @return Boolean True, if the controller panel is active
318 */
319 self.controllerPanelIsActive = function () {
320 return typeof self.activeControllerPanel !== 'undefined' && self.activeControllerPanel !== null;
321 };
322
323 /**
324 * Returns the editor
325 * @return Object The editor
326 */
327 self.getEditors = function () {
328 if (!window.tinymce || !window.tinymce.editors || window.tinymce.editors.length === 0) {
329 return [];
330 }
331
332 return window.tinymce.editors || [];
333 };
334
335 /**
336 * Parses the content, sending it to the component which will
337 * look for urls to be parsed into embed codes
338 *
339 * @param string content The content
340 * @param function onsuccess The callback called on success
341 * @return void
342 */
343 self.getParsedContent = function (content, onsuccess) {
344 // Get the parsed content
345 $.ajax({
346 type: 'POST',
347 url: self.params.baseUrl + 'wp-admin/admin-ajax.php',
348 data: {
349 action: 'embedpress_do_ajax_request',
350 subject: content
351 },
352 success: onsuccess,
353 dataType: 'json',
354 async: true
355 });
356 };
357
358 self.addStylesheet = function (url, editorInstance) {
359 var head = editorInstance.getDoc().getElementsByTagName('head')[0];
360
361 var $style = $('<link rel="stylesheet" type="text/css" href="' + url + '">');
362 $style.appendTo(head);
363 };
364
365 self.convertURLSchemeToPattern = function (scheme) {
366 var prefix = '(.*)((?:http|embedpress)s?:\\/\\/(?:www\\.)?',
367 suffix = '[\\/]?)(.*)',
368 pattern;
369
370 scheme = scheme.replace(/\*/g, '[a-zA-Z0-9=&_\\-\\?\\.\\/!\\+%:@,#]+');
371 scheme = scheme.replace(/\./g, '\\.');
372 scheme = scheme.replace(/\//g, '\\/');
373
374 return prefix + scheme + suffix;
375 };
376
377 self.getProvidersURLPatterns = function () {
378 // @todo: Add option to disable/enable the providers
379 var patterns = [];
380
381 self.each(embedpressPreviewData.urlSchemes, function convertEachURLSchemesToPattern (scheme) {
382 patterns.push(self.convertURLSchemeToPattern(scheme));
383 });
384
385 return patterns;
386 };
387
388 self.addScript = function (source, callback, wrapper, editorInstance) {
389 var doc = editorInstance.getDoc();
390
391 if (typeof wrapper === 'undefined' || !wrapper) {
392 wrapper = $(doc.getElementsByTagName('head')[0]);
393 }
394
395 var $script = $(doc.createElement('script'));
396 $script.attr('async', 1);
397
398 if (typeof callback === 'function') {
399 $script.ready(callback);
400 }
401
402 $script.attr('src', source);
403
404 wrapper.append($script);
405 };
406
407 self.addScriptDeclaration = function (wrapper, declaration, editorInstance) {
408 var doc = editorInstance.getDoc(),
409 $script = $(doc.createElement('script'));
410
411 $(wrapper).append($script);
412
413 $script.text(declaration);
414 };
415
416 self.addURLsPlaceholder = function (node, url, editorInstance) {
417 var uid = self.makeId();
418
419 var wrapperClasses = ['embedpress_wrapper', 'embedpress_placeholder', 'wpview', 'wpview-wrap'];
420
421 var shortcodeAttributes = node.value.getShortcodeAttributes(embedpressPreviewData.shortcode);
422 var customAttributes = shortcodeAttributes;
423
424 var customClasses = '';
425 if (!!Object.keys(shortcodeAttributes).length) {
426 var specialAttributes = ['class', 'href', 'data-href'];
427 // Iterates over each attribute of shortcodeAttributes to add the prefix "data-" if missing
428 var dataPrefix = 'data-';
429 var prefixedShortcodeAttributes = [];
430 for (var attr in shortcodeAttributes) {
431 if (specialAttributes.indexOf(attr) === -1) {
432 if (attr.indexOf(dataPrefix) !== 0) {
433 prefixedShortcodeAttributes[dataPrefix + attr] = shortcodeAttributes[attr];
434 } else {
435 prefixedShortcodeAttributes[attr] = shortcodeAttributes[attr];
436 }
437 } else {
438 attr = attr.replace(dataPrefix, '');
439 if (attr === 'class') {
440 wrapperClasses.push(shortcodeAttributes[attr]);
441 }
442 }
443 }
444
445 shortcodeAttributes = prefixedShortcodeAttributes;
446 prefixedShortcodeAttributes = dataPrefix = null;
447 }
448
449 if (('data-width' in shortcodeAttributes || 'data-height' in shortcodeAttributes) && 'data-responsive' in shortcodeAttributes) {
450 shortcodeAttributes['data-responsive'] = 'false';
451 }
452
453 var wrapper = new self.Node('div', 1);
454 var wrapperSettings = {
455 'class': Array.from(new Set(wrapperClasses)).join(' '),
456 'data-url': url,
457 'data-uid': uid,
458 'id': 'embedpress_wrapper_' + uid,
459 'data-loading-text': 'Loading your embed...'
460 };
461
462 wrapperSettings = $.extend({}, wrapperSettings, shortcodeAttributes);
463
464 if (wrapperSettings.class.indexOf('is-loading') === -1) {
465 wrapperSettings.class += ' is-loading';
466 }
467
468 wrapper.attr(wrapperSettings);
469
470 var panel = new self.Node('div', 1);
471 panel.attr({
472 'id': 'embedpress_controller_panel_' + uid,
473 'class': 'embedpress_controller_panel embedpress_ignore_mouseout hidden'
474 });
475 wrapper.append(panel);
476
477 function createGhostNode (htmlTag, content) {
478 htmlTag = htmlTag || 'span';
479 content = content || '&nbsp;';
480
481 var ghostNode = new self.Node(htmlTag, 1);
482 ghostNode.attr({
483 'class': 'hidden'
484 });
485
486 var ghostText = new self.Node('#text', 3);
487 ghostText.value = content;
488 ghostNode.append(ghostText);
489
490 return ghostNode;
491 }
492
493 var editButton = new self.Node('div', 1);
494 editButton.attr({
495 'id': 'embedpress_button_edit_' + uid,
496 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
497 });
498 var editButtonIcon = new self.Node('div', 1);
499 editButtonIcon.attr({
500 'class': 'embedpress-icon-pencil embedpress_ignore_mouseout'
501 });
502 editButtonIcon.append(createGhostNode());
503 editButton.append(editButtonIcon);
504 panel.append(editButton);
505
506 var removeButton = new self.Node('div', 1);
507 removeButton.attr({
508 'id': 'embedpress_button_remove_' + uid,
509 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
510 });
511 var removeButtonIcon = new self.Node('div', 1);
512 removeButtonIcon.attr({
513 'class': 'embedpress-icon-x embedpress_ignore_mouseout'
514 });
515 removeButtonIcon.append(createGhostNode());
516 removeButton.append(removeButtonIcon);
517 panel.append(removeButton);
518
519 node.value = node.value.trim();
520
521 node.replace(wrapper);
522
523 // Trigger the timeout which will load the content
524 window.setTimeout(function () {
525 self.parseContentAsync(uid, url, customAttributes, editorInstance);
526 }, 200);
527
528 return wrapper;
529 };
530
531 self.parseContentAsync = function (uid, url, customAttributes, editorInstance) {
532 customAttributes = typeof customAttributes === 'undefined' ? {} : customAttributes;
533
534 url = self.decodeEmbedURLSpecialChars(url, true, customAttributes);
535 var rawUrl = url.stripShortcode(embedpressPreviewData.shortcode);
536
537 $(self).triggerHandler('EmbedPress.beforeEmbed', {
538 'url': rawUrl,
539 'meta': {
540 'attributes': customAttributes || {}
541 }
542 });
543
544 // Get the parsed embed code from the EmbedPress plugin
545 self.getParsedContent(url, function getParsedContentCallback (result) {
546 var embeddedContent = (typeof result.data === 'object' ? result.data.embed : result.data).stripShortcode(embedpressPreviewData.shortcode);
547 var $wrapper = $(self.getElementInContentById('embedpress_wrapper_' + uid, editorInstance));
548 var wrapperParent = $($wrapper.parent());
549
550 // Check if $wrapper was rendered inside a <p> element.
551 if (wrapperParent.prop('tagName') && wrapperParent.prop('tagName').toUpperCase() === 'P') {
552 wrapperParent.replaceWith($wrapper);
553 // Check if there's at least one "space" after $wrapper.
554 var nextSibling = $($wrapper).next();
555 if (!nextSibling.length || nextSibling.prop('tagName').toUpperCase() !== 'P') {
556 //$('<p>&nbsp;</p>').insertAfter($wrapper);
557 }
558 nextSibling = null;
559 }
560 wrapperParent = null;
561
562 // Check if the url could not be embedded for some reason.
563 if (rawUrl === embeddedContent) {
564 // Echoes the raw url
565 $wrapper.replaceWith($('<p>' + rawUrl + '</p>'));
566 return;
567 }
568
569 $wrapper.removeClass('is-loading');
570
571 // Parse as DOM element
572 var $content;
573 try {
574 $content = $(embeddedContent);
575 } catch (err) {
576 // Fallback to a div, if the result is not a html markup, e.g. a url
577 $content = $('<div>');
578 $content.html(embeddedContent);
579 }
580
581 if (!$('iframe', $content).length && result.data.provider_name!=='Infogram') {
582 var contentWrapper = $($content).clone();
583 contentWrapper.html('');
584
585 $wrapper.removeClass('embedpress_placeholder');
586
587 $wrapper.append(contentWrapper);
588
589 setTimeout(function () {
590 editorInstance.undoManager.transact(function () {
591 var iframe = editorInstance.getDoc().createElement('iframe');
592 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
593 iframe.frameBorder = '0';
594 iframe.allowTransparency = 'true';
595 iframe.scrolling = 'no';
596 iframe.class = 'wpview-sandbox';
597 iframe.style.width = '100%';
598
599 contentWrapper.append(iframe);
600
601 var iframeWindow = iframe.contentWindow;
602 // Content failed to load.
603 if (!iframeWindow) {
604 return;
605 }
606
607 var iframeDoc = iframeWindow.document;
608
609 $(iframe).load(function () {
610 var maximumChecksAllowed = 8;
611 var checkIndex = 0;
612
613 var checkerInterval = setInterval(function () {
614 if (checkIndex === maximumChecksAllowed) {
615 clearInterval(checkerInterval);
616
617 setTimeout(function () {
618 $wrapper.css('width', iframe.width);
619 $wrapper.css('height', iframe.height);
620 }, 100);
621 } else {
622 if (customAttributes.height) {
623 iframe.height = customAttributes.height;
624 iframe.style.height = customAttributes.height + 'px';
625 } else {
626 iframe.height = $('body', iframeDoc).height();
627 }
628
629 if (customAttributes.width) {
630 iframe.width = customAttributes.width;
631 iframe.style.width = customAttributes.width + 'px';
632 } else {
633 iframe.width = $('body', iframeDoc).width();
634 }
635
636 checkIndex++;
637 }
638 }, 250);
639 });
640
641 iframeDoc.open();
642 iframeDoc.write(
643 '<!DOCTYPE html>' +
644 '<html>' +
645 '<head>' +
646 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
647 '<style>' +
648 'html {' +
649 'background: transparent;' +
650 'padding: 0;' +
651 'margin: 0;' +
652 '}' +
653 'body#wpview-iframe-sandbox {' +
654 'background: transparent;' +
655 'padding: 1px 0 !important;' +
656 'margin: -1px 0 0 !important;' +
657 '}' +
658 'body#wpview-iframe-sandbox:before,' +
659 'body#wpview-iframe-sandbox:after {' +
660 'display: none;' +
661 'content: "";' +
662 '}' +
663 '</style>' +
664 '</head>' +
665 '<body id="wpview-iframe-sandbox" class="' + editorInstance.getBody().className + '" style="display: inline-block; width: 100%;" >' +
666 $content.html() +
667 '</body>' +
668 '</html>'
669 );
670 iframeDoc.close();
671 });
672 }, 50);
673 } else {
674 $wrapper.removeClass('embedpress_placeholder');
675
676 self.appendElementsIntoWrapper($content, $wrapper, editorInstance);
677 }
678
679 $wrapper.append($('<span class="wpview-end"></span>'));
680
681 if (result && result.data && typeof result.data === 'object') {
682 result.data.width = $($wrapper).width();
683 result.data.height = $($wrapper).height();
684 }
685
686 $(self).triggerHandler('EmbedPress.afterEmbed', {
687 'meta': result.data,
688 'url': rawUrl,
689 'wrapper': $wrapper
690 });
691 });
692 };
693
694 self.appendElementsIntoWrapper = function (elementsList, wrapper, editorInstance) {
695 if (elementsList.length > 0) {
696 $.each(elementsList, function appendElementIntoWrapper (elementIndex, element) {
697 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
698 if (element.tagName && element.tagName.toLowerCase() !== 'script') {
699 wrapper.append($(element));
700
701 if (element.tagName.toLowerCase() === 'iframe') {
702 $(element).ready(function () {
703 window.setTimeout(function () {
704 $.each(editorInstance.dom.select('div.embedpress_wrapper iframe'), function (elementIndex, iframe) {
705 self.fixIframeSize(iframe);
706 });
707 }, 300);
708 });
709 } else if (element.tagName.toLowerCase() === 'div') {
710 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
711 // This ensures that the embed wrapper have the same width as its content
712 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
713 }
714
715 $(element).css('max-width', $($(element).parents('body').get(0)).width());
716 }
717 }
718
719 self.loadAsyncDynamicJsCodeFromElement(element, wrapper, editorInstance);
720 });
721 }
722
723 return wrapper;
724 };
725
726 self.encodeEmbedURLSpecialChars = function (content) {
727 if (content.match(SHORTCODE_REGEXP)) {
728 var subject = content.replace(SHORTCODE_REGEXP, '');
729
730 if (!subject.isValidUrl()) {
731 return content;
732 }
733
734 content = subject;
735 subject = null;
736 }
737
738 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
739 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
740
741 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
742 content = content.replace('@', '::__at__::').trim();
743
744 return content;
745 };
746
747 self.decodeEmbedURLSpecialChars = function (content, applyShortcode, attributes) {
748 var encodingRegexpRule = /embedpress(s?):\/\//;
749 applyShortcode = (typeof applyShortcode === 'undefined') ? true : applyShortcode;
750 attributes = (typeof attributes === 'undefined') ? {} : attributes;
751
752 var isEncoded = content.match(encodingRegexpRule);
753
754 // Restore http[s] in the url (converted to bypass autolink plugin)
755 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
756 content = content.replace('::__at__::', '@').trim();
757
758 if ('class' in attributes) {
759 var classesList = attributes.class.split(/\s/g);
760 var shouldRemoveDynamicWidthClass = false;
761 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
762 if (classesList[classIndex] === 'dynamic-width') {
763 shouldRemoveDynamicWidthClass = classIndex;
764 break;
765 }
766 }
767
768 if (shouldRemoveDynamicWidthClass !== false) {
769 classesList.splice(shouldRemoveDynamicWidthClass, 1);
770
771 if (classesList.length === 0) {
772 delete attributes.class;
773 }
774
775 attributes.class = classesList.join(' ');
776 }
777
778 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
779 }
780
781 if (isEncoded && applyShortcode) {
782 var shortcode = '[' + embedpressPreviewData.shortcode;
783 if (!!Object.keys(attributes).length) {
784 var attrValue;
785
786 for (var attrName in attributes) {
787 attrValue = attributes[attrName];
788
789 // Prevent `class` property being empty an treated as a boolean param
790 if (attrName.toLowerCase() === 'class' && !attrValue.length) {
791
792 } else {
793 if (attrValue.isBoolean()) {
794 shortcode += ' ';
795 if (attrValue.isFalse()) {
796 shortcode += '!';
797 }
798
799 shortcode += attrName;
800 } else {
801 shortcode += ' ' + attrName + '="' + attrValue + '"';
802 }
803 }
804 }
805 attrValue = attrName = null;
806 }
807
808 content = shortcode + ']' + content + '[/' + embedpressPreviewData.shortcode + ']';
809 }
810
811 return content;
812 };
813
814 /**
815 * Method executed after find the editor. It will make additional
816 * configurations and add the content's stylesheets for the preview
817 *
818 * @return void
819 */
820 self.onFindEditor = function (editorInstance) {
821 self.each = tinymce.each;
822 self.extend = tinymce.extend;
823 self.JSON = tinymce.util.JSON;
824 self.Node = tinymce.html.Node;
825
826 function onFindEditorCallback () {
827 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID, editorInstance, editorInstance);
828 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID, editorInstance, editorInstance);
829 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID, editorInstance, editorInstance);
830 self.addEvent('nodechange', editorInstance, self.onNodeChange);
831 self.addEvent('keydown', editorInstance, function (e) {
832 self.onKeyDown(e, editorInstance);
833 });
834
835 var onUndoCallback = function (e) {
836 self.onUndo(e, editorInstance);
837 };
838
839 self.addEvent('undo', editorInstance, onUndoCallback); // TinyMCE
840 self.addEvent('undo', editorInstance.undoManager, onUndoCallback); // JCE
841
842 var doc = editorInstance.getDoc();
843 $(doc).on('mouseenter', '.embedpress_wrapper', function (e) {
844 self.onMouseEnter(e, editorInstance);
845 });
846 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
847 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', function (e) {
848 self.cancelEvent(e, editorInstance);
849 });
850 doc = null;
851
852 // Add the node filter that will convert the url into the preview box for the embed code
853 editorInstance.parser.addNodeFilter('#text', function addNodeFilterIntoParser (nodes, arg) {
854 self.each(nodes, function eachNodeInParser (node) {
855 // Stop if the node is "isolated". It would generate an error in the browser console and break.
856 if (node.parent === null && node.prev === null) {
857 return;
858 }
859
860 var subject = node.value.trim();
861
862 if (!subject.isValidUrl()) {
863 if (!subject.match(SHORTCODE_REGEXP)) {
864 return;
865 }
866 }
867 subject = self.decodeEmbedURLSpecialChars(subject);
868 if (!subject.isValidUrl()) {
869 if (!subject.match(SHORTCODE_REGEXP)) {
870 return;
871 }
872 }
873
874 subject = node.value.stripShortcode(embedpressPreviewData.shortcode).trim();
875
876 // These patterns need to have groups for the pre and post texts
877 // @TODO: maybe remove this list of URLs? Let the server side code decide what URL should be parsed
878 var patterns = self.getProvidersURLPatterns();
879
880 (function tryToMatchContentAgainstUrlPatternWithIndex (urlPatternIndex) {
881 if (urlPatternIndex < patterns.length) {
882 var urlPattern = patterns[urlPatternIndex];
883 var urlPatternRegex = new RegExp(urlPattern);
884
885 var url = self.decodeEmbedURLSpecialChars(subject).trim();
886
887 var matches = url.match(urlPatternRegex);
888 // Check if content matches the url pattern.
889 if (matches && matches !== null && !!matches.length) {
890 url = self.encodeEmbedURLSpecialChars(matches[2]);
891
892 var wrapper = self.addURLsPlaceholder(node, url, editorInstance);
893
894 setTimeout(function () {
895 var doc = editorInstance.getDoc();
896
897 if (doc === null) {
898 return;
899 }
900
901 var previewWrapper = $(doc.querySelector('#' + wrapper.attributes.map['id']));
902 var previewWrapperParent = $(previewWrapper.parent());
903
904 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === 'P') {
905 previewWrapperParent.replaceWith(previewWrapper);
906 }
907
908 var previewWrapperOlderSibling = previewWrapper.prev();
909 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === 'P' && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
910 previewWrapperOlderSibling.remove();
911 } else {
912 if (typeof previewWrapperOlderSibling.html() !== 'undefined') {
913 if (previewWrapperOlderSibling.html().match(/<[\/]?br>/)) {
914 if (!previewWrapperOlderSibling.prev().length) {
915 previewWrapperOlderSibling.remove();
916 }
917 }
918 }
919 }
920
921 var previewWrapperYoungerSibling = previewWrapper.next();
922 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === 'P') {
923 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
924 previewWrapperYoungerSibling.remove();
925 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
926 }
927 } else {
928 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
929 }
930
931 setTimeout(function () {
932 editorInstance.selection.select(editorInstance.getBody(), true);
933 editorInstance.selection.collapse(false);
934 }, 50);
935 }, 50);
936 } else {
937 // No match. So we move on to check the next url pattern.
938 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
939 }
940 }
941 })(0);
942 });
943 });
944
945 // Add the filter that will convert the preview box/embed code back to the raw url
946 editorInstance.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer (nodes, arg) {
947 self.each(nodes, function eachNodeInSerializer (node) {
948 // Stop if the node is "isolated". It would generate an error in the browser console and break.
949 if (node.parent === null && node.prev === null) {
950 return;
951 }
952
953 var nodeClasses = (node.attributes.map.class || '').split(' ');
954 var wrapperFactoryClasses = ['embedpress_wrapper', 'embedpress_placeholder', 'wpview', 'wpview-wrap'];
955
956 var isWrapped = nodeClasses.filter(function (n) {
957 return wrapperFactoryClasses.indexOf(n) != -1;
958 }).length > 0;
959
960 if (isWrapped) {
961 var factoryAttributes = ['id', 'style', 'data-loading-text', 'data-uid', 'data-url'];
962 var customAttributes = {};
963 var dataPrefix = 'data-';
964 for (var attr in node.attributes.map) {
965 if (attr.toLowerCase() !== 'class') {
966 if (factoryAttributes.indexOf(attr) < 0) {
967 // Remove the "data-" prefix for more readability
968 customAttributes[attr.replace(dataPrefix, '')] = node.attributes.map[attr];
969 }
970 } else {
971 var customClasses = [];
972 for (var wrapperClassIndex in nodeClasses) {
973 var wrapperClass = nodeClasses[wrapperClassIndex];
974 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
975 customClasses.push(wrapperClass);
976 }
977 }
978
979 if (!!customClasses.length) {
980 customAttributes.class = customClasses.join(' ');
981 }
982 }
983 }
984
985 var p = new self.Node('p', 1);
986
987 var text = new self.Node('#text', 3);
988 text.value = (node.attributes.map && typeof node.attributes.map['data-url'] != 'undefined') ? self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes) : '';
989
990 p.append(text.clone());
991
992 node.replace(text);
993 text.replace(p);
994 }
995 });
996 });
997
998 editorInstance.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer (nodes, arg) {
999 self.each(nodes, function eachNodeInSerializer (node) {
1000 if (node.firstChild == node.lastChild) {
1001 if (node.firstChild && 'value' in node.firstChild && (node.firstChild.value === '&nbsp;' || !node.firstChild.value.trim().length)) {
1002 node.remove();
1003 }
1004 }
1005 });
1006 });
1007
1008 //@todo:isthiseachreallynecessary?
1009 // Add event to reconfigure wrappers every time the content is loaded
1010 tinymce.each(tinymce.editors, function onEachEditor (editor) {
1011 self.addEvent('loadContent', editor, function onInitEditor (ed) {
1012 self.configureWrappers(editor);
1013 });
1014 });
1015
1016 // Add the edit form
1017
1018 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
1019 window.setTimeout(
1020 function afterTimeoutOnFindEditor () {
1021 /*
1022 * This is required because after load/refresh the page, the
1023 * onLoadContent is not being triggered automatically, so
1024 * we force the event
1025 */
1026 editorInstance.load();
1027 },
1028 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1029 500
1030 );
1031 }
1032
1033 // Let's make sure the inner doc has been fully loaded first.
1034 var checkTimesLimit = 100;
1035 var checkIndex = 0;
1036 var statusCheckerInterval = setInterval(function () {
1037 if (checkIndex === checkTimesLimit) {
1038 clearInterval(statusCheckerInterval);
1039 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1040 } else {
1041 var doc = editorInstance.getDoc();
1042 if (doc) {
1043 clearInterval(statusCheckerInterval);
1044 onFindEditorCallback();
1045 } else {
1046 checkIndex++;
1047 }
1048 }
1049 }, 250);
1050 };
1051
1052 self.fixIframeSize = function (iframe) {
1053 var maxWidth = 480;
1054 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1055 var ratio = $(iframe).height() / $(iframe).width();
1056 $(iframe).width(maxWidth);
1057 $(iframe).height(maxWidth * ratio);
1058 $(iframe).css('max-width', maxWidth);
1059 $(iframe).attr('max-width', maxWidth);
1060
1061 $(iframe).data('size-fixed', true);
1062 }
1063 };
1064
1065 /**
1066 * Function triggered on mouse enter the wrapper
1067 *
1068 * @param object e The event
1069 * @return void
1070 */
1071 self.onMouseEnter = function (e, editorInstance) {
1072 self.displayPreviewControllerPanel($(e.currentTarget), editorInstance);
1073 };
1074
1075 /**
1076 * Function triggered on mouse get out of the wrapper
1077 *
1078 * @param object e The event
1079 * @return void
1080 */
1081 self.onMouseOut = function (e) {
1082 // Check if the destiny is not a child element
1083 // Chrome
1084 if (self.isDefined(e.toElement)) {
1085 if (e.toElement.parentElement == e.fromElement
1086 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1087 ) {
1088 return false;
1089 }
1090 }
1091
1092 // Firefox
1093 if (self.isDefined(e.relatedTarget)) {
1094 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1095 return false;
1096 }
1097 }
1098
1099 self.hidePreviewControllerPanel();
1100 };
1101
1102 /**
1103 * Callback triggered by paste events. This should be hooked by TinyMCE's paste_preprocess
1104 * setting. A normal bind to the onPaste event doesn't work correctly all the times
1105 * (specially when you copy and paste content from the same editor).
1106 *
1107 * @param mixed - plugin
1108 * @param mixed - args
1109 *
1110 * @return void
1111 */
1112
1113 self.onPaste = function (plugin, args) {
1114 var urlPatternRegex = new RegExp(/(https?):\/\/([w]{3}\.)?.+?(?:\s|$)/i);
1115 var urlPatternsList = self.getProvidersURLPatterns();
1116
1117 // Split the pasted content into separated lines.
1118 var contentLines = args.content.split(/\n/g) || [];
1119 contentLines = contentLines.map(function (line, itemIndex) {
1120 // Check if there's a url into `line`.
1121 if (line.match(urlPatternRegex)) {
1122 // Split the current line across its space-characters to isolate the url.
1123 let termsList = line.trim().split(/\s+/);
1124 termsList = termsList.map(function (term, termIndex) {
1125 // Check if the term into the current line is a url.
1126 var match = term.match(urlPatternRegex);
1127 if (match) {
1128 for (var urlPatternIndex = 0; urlPatternIndex < urlPatternsList.length; urlPatternIndex++) {
1129 // Isolates that url from the rest of the content if the service is supported.
1130 var urlPattern = new RegExp(urlPatternsList[urlPatternIndex]);
1131 if (urlPattern.test(term)) {
1132 return '</p><p>' + match[0] + '</p><p>';
1133 }
1134 }
1135 }
1136
1137 return term;
1138 });
1139
1140 termsList[termsList.length - 1] = termsList[termsList.length - 1] + '<br>';
1141
1142 line = termsList.join(' ');
1143 }
1144
1145 return line;
1146 });
1147
1148 // Check if the text was transformed or not. If it was, add wrappers
1149 var content = contentLines.join('');
1150
1151 if (content.replace(/<br>$/, '') !== args.content) {
1152 args.content = '<p>' + args.content + '</p>';
1153 }
1154 };
1155
1156 /**
1157 * Method trigered on every node change, to detect new lines. It will
1158 * try to fix a default behavior for some editors of clone the parent
1159 * element when adding a line break. This will clone the embed wrapper
1160 * if we set the cursor after a preview wrapper and hit enter.
1161 *
1162 * @param object e The event
1163 * @return void
1164 */
1165 self.onNodeChange = function (e) {
1166 // Fix the clone parent on break lines issue
1167 // Check if a line break was added
1168 if (e.element.tagName === 'BR') {
1169 // Check one of the parent elements is a clonned embed wrapper
1170 if (e.parents.length > 0) {
1171 $.each(e.parents, function (index, parent) {
1172 if ($(parent).hasClass('embedpress_wrapper')) {
1173 // Remove the cloned wrapper and replace with a 'br' tag
1174 $(parent).replaceWith($('<br>'));
1175 }
1176 });
1177 }
1178 } else if (e.element.tagName === 'IFRAME') {
1179 if (e.parents.length > 0) {
1180 $.each(e.parents, function (index, parent) {
1181 parent = $(parent);
1182 if (parent.hasClass('embedpress_wrapper')) {
1183 var wrapper = $('.embedpress-wrapper', parent);
1184 if (wrapper.length > 1) {
1185 wrapper.get(0).remove();
1186 }
1187 }
1188 });
1189 }
1190 }
1191 };
1192
1193 self.onKeyDown = function (e, editorInstance) {
1194 var node = editorInstance.selection.getNode();
1195
1196 if (e.keyCode == 8 || e.keyCode == 46) {
1197 if (node.nodeName.toLowerCase() === 'p') {
1198 var children = $(node).children();
1199 if (children.length > 0) {
1200 $.each(children, function () {
1201 // On delete, make sure to remove the wrapper and children, not only the wrapper
1202 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1203 $(this).remove();
1204
1205 editorInstance.focus();
1206 }
1207 });
1208 }
1209 }
1210 } else {
1211 // Ignore the arrows keys
1212 var arrowsKeyCodes = [37, 38, 39, 40];
1213 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1214
1215 // Check if we are inside a preview wrapper
1216 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1217 // Avoid delete the wrapper or block line break if we are inside the wrapper
1218 if (e.keyCode == 13) {
1219 wrapper = $(self.getWrapperFromChild(node));
1220 if (wrapper.length > 0) {
1221 // Creates a temporary element which will be inserted after the wrapper
1222 var tmpId = '__embedpress__tmp_' + self.makeId();
1223 wrapper.after($('<span id="' + tmpId + '"></span>'));
1224 // Get and select the temporary element
1225 var span = editorInstance.dom.select('span#' + tmpId)[0];
1226 editorInstance.selection.select(span);
1227 // Remove the temporary element
1228 $(span).remove();
1229 }
1230
1231 return true;
1232 } else {
1233 // If we are inside the embed preview, ignore any key to avoid edition
1234 return self.cancelEvent(e, editorInstance);
1235 }
1236 }
1237 }
1238 }
1239
1240 return true;
1241 };
1242
1243 self.getWrapperFromChild = function (element) {
1244 // Is the wrapper
1245 if ($(element).hasClass('embedpress_wrapper')) {
1246 return element;
1247 } else {
1248 var $parent = $(element).parent();
1249
1250 if ($parent.length > 0) {
1251 return self.getWrapperFromChild($parent[0]);
1252 }
1253 }
1254
1255 return false;
1256 };
1257
1258 self.onUndo = function (e, editorInstance) {
1259 // Force re-render everything
1260 editorInstance.load();
1261 };
1262
1263 self.cancelEvent = function (e, editorInstance) {
1264 e.preventDefault();
1265 e.stopPropagation();
1266 editorInstance.dom.events.cancel();
1267
1268 return false;
1269 };
1270
1271 /**
1272 * Method executed when the edit button is clicked. It will display
1273 * a field with the current url, to update the current embed's source
1274 * url.
1275 *
1276 * @param Object e The event
1277 * @return void
1278 */
1279 self.onClickEditButton = function (e, editorInstance) {
1280 // Prevent edition of the panel
1281 self.cancelEvent(e, editorInstance);
1282
1283 self.activeWrapperForModal = self.activeWrapper;
1284
1285 var $wrapper = self.activeWrapperForModal;
1286 var wrapperUid = $wrapper.prop('id').replace('embedpress_wrapper_', '');
1287
1288 var customAttributes = {};
1289
1290 var $embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1291 var embedItem = $('iframe', $wrapper);
1292 if (!embedItem.length) {
1293 embedItem = null;
1294 }
1295 if ($embedInnerWrapper.length > 1){
1296 $.each($embedInnerWrapper[0].attributes, function () {
1297 if (this.specified) {
1298 if (this.name !== 'class') {
1299 customAttributes[this.name.replace('data-', '').toLowerCase()] = this.value;
1300 }
1301 }
1302 });
1303 }
1304
1305
1306 var embedWidth = (((embedItem && embedItem.width()) || $embedInnerWrapper.data('width')) || $embedInnerWrapper.width()) || '';
1307 var embedHeight = (((embedItem && embedItem.height()) || $embedInnerWrapper.data('height')) || $embedInnerWrapper.height()) || '';
1308
1309 embedItem = $embedInnerWrapper = null;
1310
1311 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1312
1313 setTimeout(function () {
1314 $.ajax({
1315 type: 'GET',
1316 url: self.params.baseUrl + 'wp-admin/admin-ajax.php',
1317 data: {
1318 action: 'embedpress_get_embed_url_info',
1319 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1320 },
1321 beforeSend: function (request, requestSettings) {
1322 $('.loader-indicator', $wrapper).addClass('is-loading');
1323 },
1324 success: function (response) {
1325 if (!response) {
1326 alert('Unable to get a valid response from the server.');
1327 return;
1328 }
1329 if (response.canBeResponsive) {
1330 var embedShouldBeResponsive = true;
1331 if ('width' in customAttributes || 'height' in customAttributes) {
1332 embedShouldBeResponsive = false;
1333 } else if ('responsive' in customAttributes && customAttributes['responsive'].isFalse()) {
1334 embedShouldBeResponsive = false;
1335 }
1336 }
1337
1338 // Create modern modal dialog
1339 var modalHtml = '<div class="embedpress-modal-overlay" id="modal-' + wrapperUid + '">' +
1340 '<div class="embedpress-modal">' +
1341 '<div class="embedpress-modal-header">' +
1342 '<h3>Editing Embed properties</h3>' +
1343 '<button type="button" class="embedpress-modal-close">&times;</button>' +
1344 '</div>' +
1345 '<div class="embedpress-modal-body">' +
1346 '<form id="form-' + wrapperUid + '" class="embedpress-form">' +
1347 '<div class="embedpress-form-group">' +
1348 '<label for="input-url-' + wrapperUid + '">URL</label>' +
1349 '<input type="url" id="input-url-' + wrapperUid + '" value="' + self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) + '">' +
1350 '</div>' +
1351 (response.canBeResponsive ?
1352 '<div class="embedpress-form-group">' +
1353 '<label>Responsive</label>' +
1354 '<div class="embedpress-radio-group">' +
1355 '<label><input type="radio" name="input-responsive-' + wrapperUid + '" value="1"' + (embedShouldBeResponsive ? ' checked' : '') + '> Yes</label>' +
1356 '<label><input type="radio" name="input-responsive-' + wrapperUid + '" value="0"' + (!embedShouldBeResponsive ? ' checked' : '') + '> No</label>' +
1357 '</div>' +
1358 '</div>' : '') +
1359 '<div class="embedpress-form-row">' +
1360 '<div class="embedpress-form-group">' +
1361 '<label for="input-width-' + wrapperUid + '">Width</label>' +
1362 '<input type="number" id="input-width-' + wrapperUid + '" value="' + embedWidth + '"' + (embedShouldBeResponsive ? ' disabled' : '') + '>' +
1363 '</div>' +
1364 '<div class="embedpress-form-group">' +
1365 '<label for="input-height-' + wrapperUid + '">Height</label>' +
1366 '<input type="number" id="input-height-' + wrapperUid + '" value="' + embedHeight + '"' + (embedShouldBeResponsive ? ' disabled' : '') + '>' +
1367 '</div>' +
1368 '</div>' +
1369 '</form>' +
1370 '</div>' +
1371 '<div class="embedpress-modal-footer">' +
1372 '<button type="button" class="embedpress-btn embedpress-btn-cancel">Cancel</button>' +
1373 '<button type="button" class="embedpress-btn embedpress-btn-primary">Save</button>' +
1374 '</div>' +
1375 '</div>' +
1376 '</div>';
1377
1378 // Add modal to page
1379 $('body').append(modalHtml);
1380 var $modal = $('#modal-' + wrapperUid);
1381
1382 // Show modal
1383 $modal.show();
1384
1385 // Handle modal events
1386 $modal.on('click', '.embedpress-modal-close, .embedpress-btn-cancel', function() {
1387 $modal.remove();
1388 self.activeWrapperForModal = null;
1389 });
1390
1391 $modal.on('click', '.embedpress-btn-primary', function() {
1392 var $wrapper = self.activeWrapperForModal;
1393
1394 // Select the current wrapper as a base for the new element
1395 editorInstance.focus();
1396 editorInstance.selection.select($wrapper[0]);
1397
1398 $wrapper.children().remove();
1399 $wrapper.remove();
1400
1401 if (response.canBeResponsive) {
1402 if ($('#form-' + wrapperUid + ' input[name="input-responsive-' + wrapperUid + '"]:checked').val() === '0') {
1403 var embedCustomWidth = $('#input-width-' + wrapperUid).val();
1404 if (parseInt(embedCustomWidth) > 0) {
1405 customAttributes['width'] = embedCustomWidth;
1406 }
1407
1408 var embedCustomHeight = $('#input-height-' + wrapperUid).val();
1409 if (parseInt(embedCustomHeight) > 0) {
1410 customAttributes['height'] = embedCustomHeight;
1411 }
1412
1413 customAttributes['responsive'] = 'false';
1414 } else {
1415 delete customAttributes['width'];
1416 delete customAttributes['height'];
1417
1418 customAttributes['responsive'] = 'true';
1419 }
1420 } else {
1421 var embedCustomWidth = $('#input-width-' + wrapperUid).val();
1422 if (parseInt(embedCustomWidth) > 0) {
1423 customAttributes['width'] = embedCustomWidth;
1424 }
1425
1426 var embedCustomHeight = $('#input-height-' + wrapperUid).val();
1427 if (parseInt(embedCustomHeight) > 0) {
1428 customAttributes['height'] = embedCustomHeight;
1429 }
1430 }
1431
1432 var customAttributesList = [];
1433 if (!!Object.keys(customAttributes).length) {
1434 for (var attrName in customAttributes) {
1435 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1436 }
1437 }
1438
1439 var shortcode = '[' + embedpressPreviewData.shortcode + (customAttributesList.length > 0 ? ' ' + customAttributesList.join(' ') : '') + ']' + $('#input-url-' + wrapperUid).val() + '[/' + embedpressPreviewData.shortcode + ']';
1440 // We do not directly replace the node because it was causing a bug on a second edit attempt
1441 editorInstance.execCommand('mceInsertContent', false, shortcode);
1442
1443 self.configureWrappers(editorInstance);
1444 $modal.remove();
1445 });
1446
1447 $modal.on('change', 'input[type="radio"]', function (e) {
1448 var $radio = $(this);
1449 var form = $radio.closest('form');
1450 var isResponsive = $radio.val() === '1';
1451
1452 $('input[type="number"]', form).prop('disabled', isResponsive);
1453 });
1454 },
1455 complete: function (request, textStatus) {
1456 $('.loader-indicator', $wrapper).removeClass('is-loading');
1457
1458 setTimeout(function () {
1459 $('.loader-indicator', $wrapper).remove();
1460 }, 350);
1461 },
1462 dataType: 'json',
1463 async: true
1464 });
1465 }, 200);
1466
1467 return false;
1468 };
1469
1470 /**
1471 * Method executed when the remove button is clicked. It will remove
1472 * the preview and embed code, adding a mark to ignore the url
1473 *
1474 * @param Object e The event
1475 * @return void
1476 */
1477 self.onClickRemoveButton = function (e, editorInstance) {
1478 // Prevent edition of the panel
1479 self.cancelEvent(e, editorInstance);
1480
1481 var $wrapper = self.activeWrapper;
1482
1483 $wrapper.children().remove();
1484 $wrapper.remove();
1485
1486 return false;
1487 };
1488
1489 self.recursivelyAddClass = function (element, className) {
1490 $(element).children().each(function (index, child) {
1491 $(child).addClass(className);
1492
1493 var grandChild = $(child).children();
1494 if (grandChild.length > 0) {
1495 self.recursivelyAddClass(child, className);
1496 }
1497 });
1498 };
1499
1500 self.setInterval = function (callback, time, timeout) {
1501 var elapsed = 0;
1502 var iteraction = 0;
1503
1504 var interval = window.setInterval(function () {
1505 elapsed += time;
1506 iteraction++;
1507
1508 if (elapsed <= timeout) {
1509 callback(iteraction, elapsed);
1510 } else {
1511 self.stopInterval(interval);
1512 }
1513 }, time);
1514
1515 return interval;
1516 };
1517
1518 self.stopInterval = function (interval) {
1519 window.clearInterval(interval);
1520 interval = null;
1521 };
1522
1523 /**
1524 * Configure unconfigured embed wrappers, adding events and css
1525 * @return void
1526 */
1527 self.configureWrappers = function (editorInstance) {
1528 window.setTimeout(
1529 function configureWrappersTimeOut () {
1530 var doc = editorInstance.getDoc(),
1531 $wrapper = null;
1532
1533 // Get all the wrappers
1534 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1535 if (wrappers.length > 0) {
1536 for (var i = 0; i < wrappers.length; i++) {
1537 $wrapper = $(wrappers[i]);
1538
1539 // Check if the wrapper wasn't already configured
1540 if ($wrapper.data('configured') != true) {
1541 // A timeout was set to avoid block the content loading
1542 window.setTimeout(function () {
1543 // @todo: Check if we need a limit of levels to avoid use too much resources
1544 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1545 }, 500);
1546
1547 // Fix the wrapper size. Wait until find the child iframe. L
1548 var interval = self.setInterval(function (iteraction) {
1549 var $childIframes = $wrapper.find('iframe');
1550
1551 if ($childIframes.length > 0) {
1552 $.each($childIframes, function (index, iframe) {
1553 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1554 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1555 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1556 ) {
1557 $wrapper.css('width', $(iframe).width() + 'px');
1558 self.stopInterval(interval);
1559 }
1560 });
1561 }
1562 }, 500, 8000);
1563
1564 $wrapper.data('configured', true);
1565 }
1566 }
1567 }
1568 },
1569 200
1570 );
1571 };
1572
1573 /**
1574 * Hide the controller panel
1575 *
1576 * @return void
1577 */
1578 self.hidePreviewControllerPanel = function () {
1579 if (self.controllerPanelIsActive()) {
1580 $(self.activeControllerPanel).addClass('hidden');
1581 self.activeControllerPanel = null;
1582 self.activeWrapper = null;
1583 }
1584 };
1585
1586 /**
1587 * Get an element by id in the editor's content
1588 *
1589 * @param String id The element id
1590 * @return Element The found element or null, wrapped by jQuery
1591 */
1592 self.getElementInContentById = function (id, editorInstance) {
1593 var doc = editorInstance.getDoc();
1594
1595 if (doc === null) {
1596 return;
1597 }
1598
1599 return $(doc.getElementById(id));
1600 };
1601
1602 /**
1603 * Show the controller panel
1604 *
1605 * @param element $wrapper The wrapper which will be activate
1606 * @return void
1607 */
1608 self.displayPreviewControllerPanel = function ($wrapper, editorInstance) {
1609 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1610 self.hidePreviewControllerPanel();
1611 }
1612
1613 if (!self.controllerPanelIsActive() && !$wrapper.hasClass('is-loading')) {
1614 var uid = $wrapper.data('uid');
1615 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid, editorInstance);
1616
1617 if (!$panel.data('event-set')) {
1618 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid, editorInstance);
1619 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid, editorInstance);
1620
1621 self.addEvent('mousedown', $editButton, function (e) {
1622 self.onClickEditButton(e, editorInstance);
1623 });
1624
1625 self.addEvent('mousedown', $removeButton, function (e) {
1626 self.onClickRemoveButton(e, editorInstance);
1627 });
1628
1629 $panel.data('event-set', true);
1630 }
1631
1632 // Update the position of the control bar
1633 var next = $panel.next()[0];
1634 if (typeof next !== 'undefined') {
1635 if (next.nodeName.toLowerCase() === 'iframe') {
1636 $panel.css('left', ($(next).width() / 2));
1637 }
1638 }
1639
1640 // Show the bar
1641 $panel.removeClass('hidden');
1642
1643 self.activeControllerPanel = $panel;
1644 self.activeWrapper = $wrapper;
1645 }
1646 };
1647 };
1648
1649 if (!window.EmbedPress) {
1650 window.EmbedPress = new EmbedPress();
1651 }
1652
1653 // Initialize EmbedPress for all the current editors.
1654 window.EmbedPress.init(embedpressPreviewData.previewSettings);
1655 });
1656 })(jQuery, String, embedpressPreviewData);
1657