PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.3.1
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.3.1
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 7 years ago admin.js 1 year ago ads.js 1 year ago carousel.js 1 year ago carousel.min.js 2 years ago documents-viewer-script.js 3 years ago embed-ui.min.js 11 months ago front.js 1 year ago gallery-justify.js 1 year ago glider.js 1 year ago glider.min.js 2 years ago gutneberg-script.js 1 year ago index.html 7 years ago initCarousel.js 2 years ago initplyr.js 2 years ago instafeed.js 2 years ago license.js 2 years ago pdfobject.js 1 year ago plyr.js 1 year ago plyr.polyfilled.js 3 years ago preview.js 10 months ago remove-round-button.js 11 months ago settings.js 6 years ago vimeo-player.js 2 years ago ytiframeapi.js 2 years ago
preview.js
1733 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 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('\\[\/?' + $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 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($data.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($data.EMBEDPRESS_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($data.EMBEDPRESS_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($data.EMBEDPRESS_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 = '[' + $data.EMBEDPRESS_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 + '[/' + $data.EMBEDPRESS_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 $(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) + '">'));
828
829 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID, editorInstance, editorInstance);
830 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID, editorInstance, editorInstance);
831 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID, editorInstance, editorInstance);
832 self.addEvent('nodechange', editorInstance, self.onNodeChange);
833 self.addEvent('keydown', editorInstance, function (e) {
834 self.onKeyDown(e, editorInstance);
835 });
836
837 var onUndoCallback = function (e) {
838 self.onUndo(e, editorInstance);
839 };
840
841 self.addEvent('undo', editorInstance, onUndoCallback); // TinyMCE
842 self.addEvent('undo', editorInstance.undoManager, onUndoCallback); // JCE
843
844 var doc = editorInstance.getDoc();
845 $(doc).on('mouseenter', '.embedpress_wrapper', function (e) {
846 self.onMouseEnter(e, editorInstance);
847 });
848 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
849 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', function (e) {
850 self.cancelEvent(e, editorInstance);
851 });
852 doc = null;
853
854 // Add the node filter that will convert the url into the preview box for the embed code
855 editorInstance.parser.addNodeFilter('#text', function addNodeFilterIntoParser (nodes, arg) {
856 self.each(nodes, function eachNodeInParser (node) {
857 // Stop if the node is "isolated". It would generate an error in the browser console and break.
858 if (node.parent === null && node.prev === null) {
859 return;
860 }
861
862 var subject = node.value.trim();
863
864 if (!subject.isValidUrl()) {
865 if (!subject.match(SHORTCODE_REGEXP)) {
866 return;
867 }
868 }
869 subject = self.decodeEmbedURLSpecialChars(subject);
870 if (!subject.isValidUrl()) {
871 if (!subject.match(SHORTCODE_REGEXP)) {
872 return;
873 }
874 }
875
876 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
877
878 // These patterns need to have groups for the pre and post texts
879 // @TODO: maybe remove this list of URLs? Let the server side code decide what URL should be parsed
880 var patterns = self.getProvidersURLPatterns();
881
882 (function tryToMatchContentAgainstUrlPatternWithIndex (urlPatternIndex) {
883 if (urlPatternIndex < patterns.length) {
884 var urlPattern = patterns[urlPatternIndex];
885 var urlPatternRegex = new RegExp(urlPattern);
886
887 var url = self.decodeEmbedURLSpecialChars(subject).trim();
888
889 var matches = url.match(urlPatternRegex);
890 // Check if content matches the url pattern.
891 if (matches && matches !== null && !!matches.length) {
892 url = self.encodeEmbedURLSpecialChars(matches[2]);
893
894 var wrapper = self.addURLsPlaceholder(node, url, editorInstance);
895
896 setTimeout(function () {
897 var doc = editorInstance.getDoc();
898
899 if (doc === null) {
900 return;
901 }
902
903 var previewWrapper = $(doc.querySelector('#' + wrapper.attributes.map['id']));
904 var previewWrapperParent = $(previewWrapper.parent());
905
906 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === 'P') {
907 previewWrapperParent.replaceWith(previewWrapper);
908 }
909
910 var previewWrapperOlderSibling = previewWrapper.prev();
911 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === 'P' && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
912 previewWrapperOlderSibling.remove();
913 } else {
914 if (typeof previewWrapperOlderSibling.html() !== 'undefined') {
915 if (previewWrapperOlderSibling.html().match(/<[\/]?br>/)) {
916 if (!previewWrapperOlderSibling.prev().length) {
917 previewWrapperOlderSibling.remove();
918 }
919 }
920 }
921 }
922
923 var previewWrapperYoungerSibling = previewWrapper.next();
924 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === 'P') {
925 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
926 previewWrapperYoungerSibling.remove();
927 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
928 }
929 } else {
930 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
931 }
932
933 setTimeout(function () {
934 editorInstance.selection.select(editorInstance.getBody(), true);
935 editorInstance.selection.collapse(false);
936 }, 50);
937 }, 50);
938 } else {
939 // No match. So we move on to check the next url pattern.
940 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
941 }
942 }
943 })(0);
944 });
945 });
946
947 // Add the filter that will convert the preview box/embed code back to the raw url
948 editorInstance.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer (nodes, arg) {
949 self.each(nodes, function eachNodeInSerializer (node) {
950 // Stop if the node is "isolated". It would generate an error in the browser console and break.
951 if (node.parent === null && node.prev === null) {
952 return;
953 }
954
955 var nodeClasses = (node.attributes.map.class || '').split(' ');
956 var wrapperFactoryClasses = ['embedpress_wrapper', 'embedpress_placeholder', 'wpview', 'wpview-wrap'];
957
958 var isWrapped = nodeClasses.filter(function (n) {
959 return wrapperFactoryClasses.indexOf(n) != -1;
960 }).length > 0;
961
962 if (isWrapped) {
963 var factoryAttributes = ['id', 'style', 'data-loading-text', 'data-uid', 'data-url'];
964 var customAttributes = {};
965 var dataPrefix = 'data-';
966 for (var attr in node.attributes.map) {
967 if (attr.toLowerCase() !== 'class') {
968 if (factoryAttributes.indexOf(attr) < 0) {
969 // Remove the "data-" prefix for more readability
970 customAttributes[attr.replace(dataPrefix, '')] = node.attributes.map[attr];
971 }
972 } else {
973 var customClasses = [];
974 for (var wrapperClassIndex in nodeClasses) {
975 var wrapperClass = nodeClasses[wrapperClassIndex];
976 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
977 customClasses.push(wrapperClass);
978 }
979 }
980
981 if (!!customClasses.length) {
982 customAttributes.class = customClasses.join(' ');
983 }
984 }
985 }
986
987 var p = new self.Node('p', 1);
988
989 var text = new self.Node('#text', 3);
990 text.value = (node.attributes.map && typeof node.attributes.map['data-url'] != 'undefined') ? self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes) : '';
991
992 p.append(text.clone());
993
994 node.replace(text);
995 text.replace(p);
996 }
997 });
998 });
999
1000 editorInstance.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer (nodes, arg) {
1001 self.each(nodes, function eachNodeInSerializer (node) {
1002 if (node.firstChild == node.lastChild) {
1003 if (node.firstChild && 'value' in node.firstChild && (node.firstChild.value === '&nbsp;' || !node.firstChild.value.trim().length)) {
1004 node.remove();
1005 }
1006 }
1007 });
1008 });
1009
1010 //@todo:isthiseachreallynecessary?
1011 // Add event to reconfigure wrappers every time the content is loaded
1012 tinymce.each(tinymce.editors, function onEachEditor (editor) {
1013 self.addEvent('loadContent', editor, function onInitEditor (ed) {
1014 self.configureWrappers(editor);
1015 });
1016 });
1017
1018 // Add the edit form
1019
1020 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
1021 window.setTimeout(
1022 function afterTimeoutOnFindEditor () {
1023 /*
1024 * This is required because after load/refresh the page, the
1025 * onLoadContent is not being triggered automatically, so
1026 * we force the event
1027 */
1028 editorInstance.load();
1029 },
1030 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1031 500
1032 );
1033 }
1034
1035 // Let's make sure the inner doc has been fully loaded first.
1036 var checkTimesLimit = 100;
1037 var checkIndex = 0;
1038 var statusCheckerInterval = setInterval(function () {
1039 if (checkIndex === checkTimesLimit) {
1040 clearInterval(statusCheckerInterval);
1041 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1042 } else {
1043 var doc = editorInstance.getDoc();
1044 if (doc) {
1045 clearInterval(statusCheckerInterval);
1046 onFindEditorCallback();
1047 } else {
1048 checkIndex++;
1049 }
1050 }
1051 }, 250);
1052 };
1053
1054 self.fixIframeSize = function (iframe) {
1055 var maxWidth = 480;
1056 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1057 var ratio = $(iframe).height() / $(iframe).width();
1058 $(iframe).width(maxWidth);
1059 $(iframe).height(maxWidth * ratio);
1060 $(iframe).css('max-width', maxWidth);
1061 $(iframe).attr('max-width', maxWidth);
1062
1063 $(iframe).data('size-fixed', true);
1064 }
1065 };
1066
1067 /**
1068 * Function triggered on mouse enter the wrapper
1069 *
1070 * @param object e The event
1071 * @return void
1072 */
1073 self.onMouseEnter = function (e, editorInstance) {
1074 self.displayPreviewControllerPanel($(e.currentTarget), editorInstance);
1075 };
1076
1077 /**
1078 * Function triggered on mouse get out of the wrapper
1079 *
1080 * @param object e The event
1081 * @return void
1082 */
1083 self.onMouseOut = function (e) {
1084 // Check if the destiny is not a child element
1085 // Chrome
1086 if (self.isDefined(e.toElement)) {
1087 if (e.toElement.parentElement == e.fromElement
1088 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1089 ) {
1090 return false;
1091 }
1092 }
1093
1094 // Firefox
1095 if (self.isDefined(e.relatedTarget)) {
1096 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1097 return false;
1098 }
1099 }
1100
1101 self.hidePreviewControllerPanel();
1102 };
1103
1104 /**
1105 * Callback triggered by paste events. This should be hooked by TinyMCE's paste_preprocess
1106 * setting. A normal bind to the onPaste event doesn't work correctly all the times
1107 * (specially when you copy and paste content from the same editor).
1108 *
1109 * @param mixed - plugin
1110 * @param mixed - args
1111 *
1112 * @return void
1113 */
1114
1115 self.onPaste = function (plugin, args) {
1116 // Debug logging to help troubleshoot
1117 if (typeof console !== 'undefined' && console.log) {
1118 console.log('EmbedPress onPaste called with content:', args.content);
1119 }
1120
1121 // Handle cases where content might be undefined or null
1122 if (!args || !args.content) {
1123 return;
1124 }
1125
1126 // Extract URLs from HTML content (handles links copied from browser)
1127 var tempDiv = document.createElement('div');
1128 tempDiv.innerHTML = args.content;
1129
1130 // Get all anchor tags and extract href attributes
1131 var links = tempDiv.getElementsByTagName('a');
1132 var extractedUrls = [];
1133 for (var i = 0; i < links.length; i++) {
1134 if (links[i].href) {
1135 extractedUrls.push(links[i].href);
1136 }
1137 }
1138
1139 // Also check for plain text URLs in the content
1140 var textContent = tempDiv.textContent || tempDiv.innerText || args.content;
1141 var urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
1142 var plainUrls = textContent.match(urlRegex) || [];
1143
1144 // Combine all found URLs
1145 var allUrls = extractedUrls.concat(plainUrls);
1146
1147 // Remove duplicates
1148 var urls = allUrls.filter(function(url, index) {
1149 return allUrls.indexOf(url) === index;
1150 });
1151
1152 // Get all supported providers from the original system
1153 var urlPatternsList = self.getProvidersURLPatterns();
1154
1155 if (!urls || urls.length === 0) {
1156 if (typeof console !== 'undefined' && console.log) {
1157 console.log('EmbedPress: No URLs found in pasted content');
1158 }
1159 return; // No URLs found
1160 }
1161
1162 if (typeof console !== 'undefined' && console.log) {
1163 console.log('EmbedPress: Found URLs:', urls);
1164 console.log('EmbedPress: Available URL patterns count:', urlPatternsList.length);
1165 }
1166
1167 // Process each URL
1168 var hasProcessedUrls = false;
1169 for (var i = 0; i < urls.length; i++) {
1170 var url = urls[i].trim();
1171 var isSupported = false;
1172
1173 // Check if URL matches any supported provider using the original system
1174 for (var urlPatternIndex = 0; urlPatternIndex < urlPatternsList.length; urlPatternIndex++) {
1175 try {
1176 var urlPattern = new RegExp(urlPatternsList[urlPatternIndex], 'i');
1177 if (urlPattern.test(url)) {
1178 isSupported = true;
1179 if (typeof console !== 'undefined' && console.log) {
1180 console.log('EmbedPress: URL matched pattern:', urlPatternsList[urlPatternIndex]);
1181 }
1182 break;
1183 }
1184 } catch (e) {
1185 // Skip invalid regex patterns
1186 if (typeof console !== 'undefined' && console.warn) {
1187 console.warn('EmbedPress: Invalid regex pattern:', urlPatternsList[urlPatternIndex], e.message);
1188 }
1189 continue;
1190 }
1191 }
1192
1193 // If supported, replace the content with just the URL
1194 if (isSupported) {
1195 if (typeof console !== 'undefined' && console.log) {
1196 console.log('EmbedPress: Processing supported URL:', url);
1197 }
1198
1199 // For HTML content with links, replace everything with just the URL
1200 if (args.content.indexOf('<a') !== -1 || args.content.indexOf('href') !== -1) {
1201 args.content = url;
1202 hasProcessedUrls = true;
1203 } else {
1204 // For plain text, isolate the URL on its own line
1205 var escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1206 var urlPattern = new RegExp(escapedUrl, 'gi');
1207 args.content = args.content.replace(urlPattern, '</p><p>' + url + '</p><p>');
1208 hasProcessedUrls = true;
1209 }
1210 }
1211 }
1212
1213 // Only clean up HTML if we processed URLs
1214 if (hasProcessedUrls) {
1215 // Clean up the HTML only if it's not a simple URL replacement
1216 if (args.content !== urls[0]) {
1217 args.content = args.content.replace(/<p><\/p>/g, '');
1218 args.content = args.content.replace(/^<\/p>/, '');
1219 args.content = args.content.replace(/<p>$/, '');
1220
1221 // Ensure proper paragraph wrapping
1222 if (args.content && !args.content.match(/^<p>/)) {
1223 args.content = '<p>' + args.content + '</p>';
1224 }
1225 }
1226 }
1227
1228 if (typeof console !== 'undefined' && console.log) {
1229 console.log('EmbedPress: Final processed content:', args.content);
1230 }
1231 };
1232
1233 /**
1234 * Method trigered on every node change, to detect new lines. It will
1235 * try to fix a default behavior for some editors of clone the parent
1236 * element when adding a line break. This will clone the embed wrapper
1237 * if we set the cursor after a preview wrapper and hit enter.
1238 *
1239 * @param object e The event
1240 * @return void
1241 */
1242 self.onNodeChange = function (e) {
1243 // Fix the clone parent on break lines issue
1244 // Check if a line break was added
1245 if (e.element.tagName === 'BR') {
1246 // Check one of the parent elements is a clonned embed wrapper
1247 if (e.parents.length > 0) {
1248 $.each(e.parents, function (index, parent) {
1249 if ($(parent).hasClass('embedpress_wrapper')) {
1250 // Remove the cloned wrapper and replace with a 'br' tag
1251 $(parent).replaceWith($('<br>'));
1252 }
1253 });
1254 }
1255 } else if (e.element.tagName === 'IFRAME') {
1256 if (e.parents.length > 0) {
1257 $.each(e.parents, function (index, parent) {
1258 parent = $(parent);
1259 if (parent.hasClass('embedpress_wrapper')) {
1260 var wrapper = $('.embedpress-wrapper', parent);
1261 if (wrapper.length > 1) {
1262 wrapper.get(0).remove();
1263 }
1264 }
1265 });
1266 }
1267 }
1268 };
1269
1270 self.onKeyDown = function (e, editorInstance) {
1271 var node = editorInstance.selection.getNode();
1272
1273 if (e.keyCode == 8 || e.keyCode == 46) {
1274 if (node.nodeName.toLowerCase() === 'p') {
1275 var children = $(node).children();
1276 if (children.length > 0) {
1277 $.each(children, function () {
1278 // On delete, make sure to remove the wrapper and children, not only the wrapper
1279 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1280 $(this).remove();
1281
1282 editorInstance.focus();
1283 }
1284 });
1285 }
1286 }
1287 } else {
1288 // Ignore the arrows keys
1289 var arrowsKeyCodes = [37, 38, 39, 40];
1290 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1291
1292 // Check if we are inside a preview wrapper
1293 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1294 // Avoid delete the wrapper or block line break if we are inside the wrapper
1295 if (e.keyCode == 13) {
1296 wrapper = $(self.getWrapperFromChild(node));
1297 if (wrapper.length > 0) {
1298 // Creates a temporary element which will be inserted after the wrapper
1299 var tmpId = '__embedpress__tmp_' + self.makeId();
1300 wrapper.after($('<span id="' + tmpId + '"></span>'));
1301 // Get and select the temporary element
1302 var span = editorInstance.dom.select('span#' + tmpId)[0];
1303 editorInstance.selection.select(span);
1304 // Remove the temporary element
1305 $(span).remove();
1306 }
1307
1308 return true;
1309 } else {
1310 // If we are inside the embed preview, ignore any key to avoid edition
1311 return self.cancelEvent(e, editorInstance);
1312 }
1313 }
1314 }
1315 }
1316
1317 return true;
1318 };
1319
1320 self.getWrapperFromChild = function (element) {
1321 // Is the wrapper
1322 if ($(element).hasClass('embedpress_wrapper')) {
1323 return element;
1324 } else {
1325 var $parent = $(element).parent();
1326
1327 if ($parent.length > 0) {
1328 return self.getWrapperFromChild($parent[0]);
1329 }
1330 }
1331
1332 return false;
1333 };
1334
1335 self.onUndo = function (e, editorInstance) {
1336 // Force re-render everything
1337 editorInstance.load();
1338 };
1339
1340 self.cancelEvent = function (e, editorInstance) {
1341 e.preventDefault();
1342 e.stopPropagation();
1343 editorInstance.dom.events.cancel();
1344
1345 return false;
1346 };
1347
1348 /**
1349 * Method executed when the edit button is clicked. It will display
1350 * a field with the current url, to update the current embed's source
1351 * url.
1352 *
1353 * @param Object e The event
1354 * @return void
1355 */
1356 self.onClickEditButton = function (e, editorInstance) {
1357 // Prevent edition of the panel
1358 self.cancelEvent(e, editorInstance);
1359
1360 self.activeWrapperForModal = self.activeWrapper;
1361
1362 var $wrapper = self.activeWrapperForModal;
1363 var wrapperUid = $wrapper.prop('id').replace('embedpress_wrapper_', '');
1364
1365 var customAttributes = {};
1366
1367 var $embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1368 var embedItem = $('iframe', $wrapper);
1369 if (!embedItem.length) {
1370 embedItem = null;
1371 }
1372 if ($embedInnerWrapper.length > 1){
1373 $.each($embedInnerWrapper[0].attributes, function () {
1374 if (this.specified) {
1375 if (this.name !== 'class') {
1376 customAttributes[this.name.replace('data-', '').toLowerCase()] = this.value;
1377 }
1378 }
1379 });
1380 }
1381
1382
1383 var embedWidth = (((embedItem && embedItem.width()) || $embedInnerWrapper.data('width')) || $embedInnerWrapper.width()) || '';
1384 var embedHeight = (((embedItem && embedItem.height()) || $embedInnerWrapper.data('height')) || $embedInnerWrapper.height()) || '';
1385
1386 embedItem = $embedInnerWrapper = null;
1387
1388 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1389
1390 setTimeout(function () {
1391 $.ajax({
1392 type: 'GET',
1393 url: self.params.baseUrl + 'wp-admin/admin-ajax.php',
1394 data: {
1395 action: 'embedpress_get_embed_url_info',
1396 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1397 },
1398 beforeSend: function (request, requestSettings) {
1399 $('.loader-indicator', $wrapper).addClass('is-loading');
1400 },
1401 success: function (response) {
1402 if (!response) {
1403 bootbox.alert('Unable to get a valid response from the server.');
1404 return;
1405 }
1406 if (response.canBeResponsive) {
1407 var embedShouldBeResponsive = true;
1408 if ('width' in customAttributes || 'height' in customAttributes) {
1409 embedShouldBeResponsive = false;
1410 } else if ('responsive' in customAttributes && customAttributes['responsive'].isFalse()) {
1411 embedShouldBeResponsive = false;
1412 }
1413 }
1414
1415 bootbox.dialog({
1416 className: 'embedpress-modal',
1417 title: 'Editing Embed properties',
1418 message: '<form id="form-' + wrapperUid + '" embedpress>' +
1419 '<div class="row">' +
1420 '<div class="col-md-12">' +
1421 '<div class="form-group">' +
1422 '<label for="input-url-' + wrapperUid + '">Url</label>' +
1423 '<input class="form-control" type="url" id="input-url-' + wrapperUid + '" value="' + self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) + '">' +
1424 '</div>' +
1425 '</div>' +
1426 '</div>' +
1427 '<div class="row">' +
1428 (response.canBeResponsive ?
1429 '<div class="col-md-12">' +
1430 '<label>Responsive</label>' +
1431 '<div class="form-group">' +
1432 '<label class="radio-inline">' +
1433 '<input type="radio" name="input-responsive-' + wrapperUid + '" id="input-responsive-1-' + wrapperUid + '" value="1"' + (embedShouldBeResponsive ? ' checked="checked"' : '') + '> Yes' +
1434 '</label>' +
1435 '<label class="radio-inline">' +
1436 '<input type="radio" name="input-responsive-' + wrapperUid + '" id="input-responsive-0-' + wrapperUid + '" value="0"' + (!embedShouldBeResponsive ? ' checked="checked"' : '') + '> No' +
1437 '</label>' +
1438 '</div>' +
1439 '</div>' : '') +
1440 '<div class="col-md-6">' +
1441 '<div class="form-group">' +
1442 '<label for="input-width-' + wrapperUid + '">Width</label>' +
1443 '<input class="form-control" type="integer" id="input-width-' + wrapperUid + '" value="' + embedWidth + '"' + (embedShouldBeResponsive ? ' disabled' : '') + '>' +
1444 '</div>' +
1445 '</div>' +
1446 '<div class="col-md-6">' +
1447 '<div class="form-group">' +
1448 '<label for="input-height-' + wrapperUid + '">Height</label>' +
1449 '<input class="form-control" type="integer" id="input-height-' + wrapperUid + '" value="' + embedHeight + '"' + (embedShouldBeResponsive ? ' disabled' : '') + '>' +
1450 '</div>' +
1451 '</div>' +
1452 '</div>' +
1453 '</form>',
1454 buttons: {
1455 danger: {
1456 label: 'Cancel',
1457 className: 'btn-default',
1458 callback: function () {
1459 // do nothing
1460 self.activeWrapperForModal = null;
1461 }
1462 },
1463 success: {
1464 label: 'Save',
1465 className: 'btn-primary',
1466 callback: function () {
1467 var $wrapper = self.activeWrapperForModal;
1468
1469 // Select the current wrapper as a base for the new element
1470 editorInstance.focus();
1471 editorInstance.selection.select($wrapper[0]);
1472
1473 $wrapper.children().remove();
1474 $wrapper.remove();
1475
1476 if (response.canBeResponsive) {
1477 if ($('#form-' + wrapperUid + ' input[name="input-responsive-' + wrapperUid + '"]:checked').val().isFalse()) {
1478 var embedCustomWidth = $('#input-width-' + wrapperUid).val();
1479 if (parseInt(embedCustomWidth) > 0) {
1480 customAttributes['width'] = embedCustomWidth;
1481 }
1482
1483 var embedCustomHeight = $('#input-height-' + wrapperUid).val();
1484 if (parseInt(embedCustomHeight) > 0) {
1485 customAttributes['height'] = embedCustomHeight;
1486 }
1487
1488 customAttributes['responsive'] = 'false';
1489 } else {
1490 delete customAttributes['width'];
1491 delete customAttributes['height'];
1492
1493 customAttributes['responsive'] = 'true';
1494 }
1495 } else {
1496 var embedCustomWidth = $('#input-width-' + wrapperUid).val();
1497 if (parseInt(embedCustomWidth) > 0) {
1498 customAttributes['width'] = embedCustomWidth;
1499 }
1500
1501 var embedCustomHeight = $('#input-height-' + wrapperUid).val();
1502 if (parseInt(embedCustomHeight) > 0) {
1503 customAttributes['height'] = embedCustomHeight;
1504 }
1505 }
1506
1507 var customAttributesList = [];
1508 if (!!Object.keys(customAttributes).length) {
1509 for (var attrName in customAttributes) {
1510 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1511 }
1512 }
1513
1514 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? ' ' + customAttributesList.join(' ') : '') + ']' + $('#input-url-' + wrapperUid).val() + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
1515 // We do not directly replace the node because it was causing a bug on a second edit attempt
1516 editorInstance.execCommand('mceInsertContent', false, shortcode);
1517
1518 self.configureWrappers(editorInstance);
1519 }
1520 }
1521 }
1522 });
1523
1524 $('form[embedpress]').on('change', 'input[type="radio"]', function (e) {
1525 var self = $(this);
1526 var form = self.parents('form[embedpress]');
1527
1528 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
1529 });
1530 },
1531 complete: function (request, textStatus) {
1532 $('.loader-indicator', $wrapper).removeClass('is-loading');
1533
1534 setTimeout(function () {
1535 $('.loader-indicator', $wrapper).remove();
1536 }, 350);
1537 },
1538 dataType: 'json',
1539 async: true
1540 });
1541 }, 200);
1542
1543 return false;
1544 };
1545
1546 /**
1547 * Method executed when the remove button is clicked. It will remove
1548 * the preview and embed code, adding a mark to ignore the url
1549 *
1550 * @param Object e The event
1551 * @return void
1552 */
1553 self.onClickRemoveButton = function (e, editorInstance) {
1554 // Prevent edition of the panel
1555 self.cancelEvent(e, editorInstance);
1556
1557 var $wrapper = self.activeWrapper;
1558
1559 $wrapper.children().remove();
1560 $wrapper.remove();
1561
1562 return false;
1563 };
1564
1565 self.recursivelyAddClass = function (element, className) {
1566 $(element).children().each(function (index, child) {
1567 $(child).addClass(className);
1568
1569 var grandChild = $(child).children();
1570 if (grandChild.length > 0) {
1571 self.recursivelyAddClass(child, className);
1572 }
1573 });
1574 };
1575
1576 self.setInterval = function (callback, time, timeout) {
1577 var elapsed = 0;
1578 var iteraction = 0;
1579
1580 var interval = window.setInterval(function () {
1581 elapsed += time;
1582 iteraction++;
1583
1584 if (elapsed <= timeout) {
1585 callback(iteraction, elapsed);
1586 } else {
1587 self.stopInterval(interval);
1588 }
1589 }, time);
1590
1591 return interval;
1592 };
1593
1594 self.stopInterval = function (interval) {
1595 window.clearInterval(interval);
1596 interval = null;
1597 };
1598
1599 /**
1600 * Configure unconfigured embed wrappers, adding events and css
1601 * @return void
1602 */
1603 self.configureWrappers = function (editorInstance) {
1604 window.setTimeout(
1605 function configureWrappersTimeOut () {
1606 var doc = editorInstance.getDoc(),
1607 $wrapper = null;
1608
1609 // Get all the wrappers
1610 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1611 if (wrappers.length > 0) {
1612 for (var i = 0; i < wrappers.length; i++) {
1613 $wrapper = $(wrappers[i]);
1614
1615 // Check if the wrapper wasn't already configured
1616 if ($wrapper.data('configured') != true) {
1617 // A timeout was set to avoid block the content loading
1618 window.setTimeout(function () {
1619 // @todo: Check if we need a limit of levels to avoid use too much resources
1620 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1621 }, 500);
1622
1623 // Fix the wrapper size. Wait until find the child iframe. L
1624 var interval = self.setInterval(function (iteraction) {
1625 var $childIframes = $wrapper.find('iframe');
1626
1627 if ($childIframes.length > 0) {
1628 $.each($childIframes, function (index, iframe) {
1629 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1630 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1631 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1632 ) {
1633 $wrapper.css('width', $(iframe).width() + 'px');
1634 self.stopInterval(interval);
1635 }
1636 });
1637 }
1638 }, 500, 8000);
1639
1640 $wrapper.data('configured', true);
1641 }
1642 }
1643 }
1644 },
1645 200
1646 );
1647 };
1648
1649 /**
1650 * Hide the controller panel
1651 *
1652 * @return void
1653 */
1654 self.hidePreviewControllerPanel = function () {
1655 if (self.controllerPanelIsActive()) {
1656 $(self.activeControllerPanel).addClass('hidden');
1657 self.activeControllerPanel = null;
1658 self.activeWrapper = null;
1659 }
1660 };
1661
1662 /**
1663 * Get an element by id in the editor's content
1664 *
1665 * @param String id The element id
1666 * @return Element The found element or null, wrapped by jQuery
1667 */
1668 self.getElementInContentById = function (id, editorInstance) {
1669 var doc = editorInstance.getDoc();
1670
1671 if (doc === null) {
1672 return;
1673 }
1674
1675 return $(doc.getElementById(id));
1676 };
1677
1678 /**
1679 * Show the controller panel
1680 *
1681 * @param element $wrapper The wrapper which will be activate
1682 * @return void
1683 */
1684 self.displayPreviewControllerPanel = function ($wrapper, editorInstance) {
1685 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1686 self.hidePreviewControllerPanel();
1687 }
1688
1689 if (!self.controllerPanelIsActive() && !$wrapper.hasClass('is-loading')) {
1690 var uid = $wrapper.data('uid');
1691 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid, editorInstance);
1692
1693 if (!$panel.data('event-set')) {
1694 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid, editorInstance);
1695 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid, editorInstance);
1696
1697 self.addEvent('mousedown', $editButton, function (e) {
1698 self.onClickEditButton(e, editorInstance);
1699 });
1700
1701 self.addEvent('mousedown', $removeButton, function (e) {
1702 self.onClickRemoveButton(e, editorInstance);
1703 });
1704
1705 $panel.data('event-set', true);
1706 }
1707
1708 // Update the position of the control bar
1709 var next = $panel.next()[0];
1710 if (typeof next !== 'undefined') {
1711 if (next.nodeName.toLowerCase() === 'iframe') {
1712 $panel.css('left', ($(next).width() / 2));
1713 }
1714 }
1715
1716 // Show the bar
1717 $panel.removeClass('hidden');
1718
1719 self.activeControllerPanel = $panel;
1720 self.activeWrapper = $wrapper;
1721 }
1722 };
1723 };
1724
1725 if (!window.EmbedPress) {
1726 window.EmbedPress = new EmbedPress();
1727 }
1728
1729 // Initialize EmbedPress for all the current editors.
1730 window.EmbedPress.init($data.previewSettings);
1731 });
1732 })(jQuery, String, $data);
1733