PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 2.4.1
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v2.4.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 index.html 7 years ago preview.js 7 years ago settings.js 6 years 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, $data, undefined) {
9 'use strict';
10
11 $(window.document).ready(function () {
12 String.prototype.capitalizeFirstLetter = function () {
13 return this.charAt(0).toUpperCase() + this.slice(1);
14 };
15
16 String.prototype.isValidUrl = function () {
17 var rule = /^(https?|embedpresss?):\/\//i;
18
19 return rule.test(this.toString());
20 };
21
22 String.prototype.hasShortcode = function (shortcode) {
23 var shortcodeRule = new RegExp('\\[' + shortcode + '(?:\\]|.+?\\])', 'ig');
24 return !!this.toString().match(shortcodeRule);
25 };
26
27 String.prototype.stripShortcode = function (shortcode) {
28 var stripRule = new RegExp('(\\[' + shortcode + '(?:\\]|.+?\\])|\\[\\/' + shortcode + '\\])', 'ig');
29 return this.toString().replace(stripRule, '');
30 };
31
32 String.prototype.setShortcodeAttribute = function (attr, value, shortcode, replaceInsteadOfMerge) {
33 replaceInsteadOfMerge = typeof replaceInsteadOfMerge === 'undefined' ? false : replaceInsteadOfMerge;
34 var subject = this.toString();
35
36 if (subject.hasShortcode(shortcode)) {
37 var attributes = subject.getShortcodeAttributes(shortcode);
38
39 if (attributes.hasOwnProperty(attr)) {
40 if (replaceInsteadOfMerge) {
41 attributes[attr] = value;
42 } else {
43 attributes[attr] += ' ' + value;
44 }
45 } else {
46 attributes[attr] = value;
47 }
48
49 if (!!Object.keys(attributes).length) {
50 var parsedAttributes = [];
51 for (var attr in attributes) {
52 parsedAttributes.push(attr + '="' + attributes[attr] + '"');
53 }
54
55 subject = '[' + shortcode + ' ' + parsedAttributes.join(' ') + ']' + subject.stripShortcode(shortcode) + '[/' + shortcode + ']';
56 } else {
57 subject = '[' + shortcode + ']' + subject.stripShortcode(shortcode) + '[/' + shortcode + ']';
58 }
59
60 return subject;
61 } else {
62 return subject;
63 }
64 };
65
66 String.prototype.getShortcodeAttributes = function (shortcode) {
67 var subject = this.toString();
68 if (subject.hasShortcode(shortcode)) {
69 var attributes = {};
70 var propertiesString = (new RegExp(/\[embed\s*(.*?)\]/ig)).exec(subject)[1]; // Separate all shortcode attributes from the rest of the string
71 if (propertiesString.length > 0) {
72 var extractAttributesRule = new RegExp(/(\!?\w+-?\w*)(?:="(.+?)")?/ig); // Extract attributes and their values
73 var match;
74 while (match = extractAttributesRule.exec(propertiesString)) {
75 var attrName = match[1];
76 var attrValue;
77 if (match[2] === undefined) {
78 // Prevent `class` property being empty an treated as a boolean param
79 if (attrName.toLowerCase() !== 'class') {
80 if (attrName.indexOf('!') === 0) {
81 attrName = attrName.replace('!', '');
82 attrValue = 'false';
83 } else {
84 attrValue = 'true';
85 }
86
87 attributes[attrName] = attrValue;
88 }
89 } else {
90 attrValue = match[2];
91 if (attrValue.isBoolean()) {
92 attrValue = attrValue.isFalse() ? 'false' : 'true';
93 }
94
95 attributes[attrName] = attrValue;
96 }
97 }
98 match = extractAttributesRule = null;
99 }
100 propertiesString = null;
101
102 return attributes;
103 } else {
104 return {};
105 }
106 };
107
108 String.prototype.isBoolean = function () {
109 var subject = this.toString().trim().toLowerCase();
110
111 return subject.isTrue(false) || subject.isFalse();
112 };
113
114 String.prototype.isTrue = function (defaultValue) {
115 var subject = this.toString().trim().toLowerCase();
116 defaultValue = typeof defaultValue === undefined ? true : defaultValue;
117
118 switch (subject) {
119 case '':
120 defaultValue += '';
121 return !defaultValue.isFalse();
122 case '1':
123 case 'true':
124 case 'on':
125 case 'yes':
126 case 'y':
127 return true;
128 default:
129 return false;
130 }
131 };
132
133 String.prototype.isFalse = function () {
134 var subject = this.toString().trim().toLowerCase();
135
136 switch (subject) {
137 case '0':
138 case 'false':
139 case 'off':
140 case 'no':
141 case 'n':
142 case 'nil':
143 case 'null':
144 return true;
145 default:
146 return false;
147 }
148 };
149
150 var SHORTCODE_REGEXP = new RegExp('\\[\/?' + $data.EMBEDPRESS_SHORTCODE + '\\]', 'gi');
151
152 var EmbedPress = function () {
153 var self = this;
154
155 var PLG_SYSTEM_ASSETS_CSS_PATH = $data.EMBEDPRESS_URL_ASSETS + 'css';
156 var PLG_CONTENT_ASSETS_CSS_PATH = PLG_SYSTEM_ASSETS_CSS_PATH;
157
158 /**
159 * The default params
160 *
161 * @type Object
162 */
163 self.params = {
164 baseUrl: '',
165 versionUID: '0'
166 };
167
168 /**
169 * True, if user agent is iOS
170 * @type Boolean True, if is iOS
171 */
172 self.iOS = /iPad|iPod|iPhone/.test(window.navigator.userAgent);
173
174 /**
175 * The active wrapper, activated by the mouse enter event
176 * @type Element
177 */
178 self.activeWrapper = null;
179
180 self.activeWrapperForModal = null;
181
182 /**
183 * The active controller panel
184 * @type Element
185 */
186 self.activeControllerPanel = null;
187
188 /**
189 * A list containing all loaded editor instances on the page
190 * @type Array
191 */
192 self.loadedEditors = [];
193
194 /**
195 * Init the plugin
196 *
197 * @param object params Override the plugin's params
198 * @return void
199 */
200 self.init = function (params) {
201 $.extend(self.params, params);
202
203 // Fix iOS doesn't firing click events on 'standard' elements
204 if (self.iOS) {
205 $(window.document.body).css('cursor', 'pointer');
206 }
207
208 $(self.onReady);
209 };
210
211 self.addEvent = function (event, element, callback) {
212 if (typeof element.on !== 'undefined') {
213 element.on(event, callback);
214 } else {
215 if (element['on' + event.capitalizeFirstLetter()]) {
216 element['on' + event.capitalizeFirstLetter()].add(callback);
217 }
218 }
219 };
220
221 self.isEmpty = function (list) {
222 return list.length === 0;
223 };
224
225 self.isDefined = function (attribute) {
226 return (typeof attribute !== 'undefined') && (attribute !== null);
227 };
228
229 self.makeId = function () {
230 var text = '';
231 var possible = 'abcdefghijklmnopqrstuvwxyz0123456789';
232
233 for (var i = 0; i < 5; i++)
234 text += possible.charAt(Math.floor(Math.random() * possible.length));
235
236 return text;
237 };
238
239 self.loadAsyncDynamicJsCodeFromElement = function (subject, wrapper, editorInstance) {
240 subject = $(subject);
241 if (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
548 var $wrapper = $(self.getElementInContentById('embedpress_wrapper_' + uid, editorInstance));
549 var wrapperParent = $($wrapper.parent());
550
551 // Check if $wrapper was rendered inside a <p> element.
552 if (wrapperParent.prop('tagName') && wrapperParent.prop('tagName').toUpperCase() === 'P') {
553 wrapperParent.replaceWith($wrapper);
554 // Check if there's at least one "space" after $wrapper.
555 var nextSibling = $($wrapper).next();
556 if (!nextSibling.length || nextSibling.prop('tagName').toUpperCase() !== 'P') {
557 //$('<p>&nbsp;</p>').insertAfter($wrapper);
558 }
559 nextSibling = null;
560 }
561 wrapperParent = null;
562
563 // Check if the url could not be embedded for some reason.
564 if (rawUrl === embeddedContent) {
565 // Echoes the raw url
566 $wrapper.replaceWith($('<p>' + rawUrl + '</p>'));
567 return;
568 }
569
570 $wrapper.removeClass('is-loading');
571
572 // Parse as DOM element
573 var $content;
574 try {
575 $content = $(embeddedContent);
576 } catch (err) {
577 // Fallback to a div, if the result is not a html markup, e.g. a url
578 $content = $('<div>');
579 $content.html(embeddedContent);
580 }
581
582 if (!$('iframe', $content).length) {
583 var contentWrapper = $($content).clone();
584 contentWrapper.html('');
585
586 $wrapper.removeClass('embedpress_placeholder');
587
588 $wrapper.append(contentWrapper);
589
590 setTimeout(function () {
591 editorInstance.undoManager.transact(function () {
592 var iframe = editorInstance.getDoc().createElement('iframe');
593 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
594 iframe.frameBorder = '0';
595 iframe.allowTransparency = 'true';
596 iframe.scrolling = 'no';
597 iframe.class = 'wpview-sandbox';
598 iframe.style.width = '100%';
599
600 contentWrapper.append(iframe);
601
602 var iframeWindow = iframe.contentWindow;
603 // Content failed to load.
604 if (!iframeWindow) {
605 return;
606 }
607
608 var iframeDoc = iframeWindow.document;
609
610 $(iframe).load(function () {
611 var maximumChecksAllowed = 8;
612 var checkIndex = 0;
613
614 var checkerInterval = setInterval(function () {
615 if (checkIndex === maximumChecksAllowed) {
616 clearInterval(checkerInterval);
617
618 setTimeout(function () {
619 $wrapper.css('width', iframe.width);
620 $wrapper.css('height', iframe.height);
621 }, 100);
622 } else {
623 if (customAttributes.height) {
624 iframe.height = customAttributes.height;
625 iframe.style.height = customAttributes.height + 'px';
626 } else {
627 iframe.height = $('body', iframeDoc).height();
628 }
629
630 if (customAttributes.width) {
631 iframe.width = customAttributes.width;
632 iframe.style.width = customAttributes.width + 'px';
633 } else {
634 iframe.width = $('body', iframeDoc).width();
635 }
636
637 checkIndex++;
638 }
639 }, 250);
640 });
641
642 iframeDoc.open();
643 iframeDoc.write(
644 '<!DOCTYPE html>' +
645 '<html>' +
646 '<head>' +
647 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
648 '<style>' +
649 'html {' +
650 'background: transparent;' +
651 'padding: 0;' +
652 'margin: 0;' +
653 '}' +
654 'body#wpview-iframe-sandbox {' +
655 'background: transparent;' +
656 'padding: 1px 0 !important;' +
657 'margin: -1px 0 0 !important;' +
658 '}' +
659 'body#wpview-iframe-sandbox:before,' +
660 'body#wpview-iframe-sandbox:after {' +
661 'display: none;' +
662 'content: "";' +
663 '}' +
664 '</style>' +
665 '</head>' +
666 '<body id="wpview-iframe-sandbox" class="' + editorInstance.getBody().className + '" style="display: inline-block;">' +
667 $content.html() +
668 '</body>' +
669 '</html>'
670 );
671 iframeDoc.close();
672 });
673 }, 50);
674 } else {
675 $wrapper.removeClass('embedpress_placeholder');
676
677 self.appendElementsIntoWrapper($content, $wrapper, editorInstance);
678 }
679
680 $wrapper.append($('<span class="wpview-end"></span>'));
681
682 if (result && result.data && typeof result.data === 'object') {
683 result.data.width = $($wrapper).width();
684 result.data.height = $($wrapper).height();
685 }
686
687 $(self).triggerHandler('EmbedPress.afterEmbed', {
688 'meta': result.data,
689 'url': rawUrl,
690 'wrapper': $wrapper
691 });
692 });
693 };
694
695 self.appendElementsIntoWrapper = function (elementsList, wrapper, editorInstance) {
696 if (elementsList.length > 0) {
697 $.each(elementsList, function appendElementIntoWrapper (elementIndex, element) {
698 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
699 if (element.tagName.toLowerCase() !== 'script') {
700 wrapper.append($(element));
701
702 if (element.tagName.toLowerCase() === 'iframe') {
703 $(element).ready(function () {
704 window.setTimeout(function () {
705 $.each(editorInstance.dom.select('div.embedpress_wrapper iframe'), function (elementIndex, iframe) {
706 self.fixIframeSize(iframe);
707 });
708 }, 300);
709 });
710 } else if (element.tagName.toLowerCase() === 'div') {
711 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
712 // This ensures that the embed wrapper have the same width as its content
713 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
714 }
715
716 $(element).css('max-width', $($(element).parents('body').get(0)).width());
717 }
718 }
719
720 self.loadAsyncDynamicJsCodeFromElement(element, wrapper, editorInstance);
721 });
722 }
723
724 return wrapper;
725 };
726
727 self.encodeEmbedURLSpecialChars = function (content) {
728 if (content.match(SHORTCODE_REGEXP)) {
729 var subject = content.replace(SHORTCODE_REGEXP, '');
730
731 if (!subject.isValidUrl()) {
732 return content;
733 }
734
735 content = subject;
736 subject = null;
737 }
738
739 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
740 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
741
742 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
743 content = content.replace('@', '::__at__::').trim();
744
745 return content;
746 };
747
748 self.decodeEmbedURLSpecialChars = function (content, applyShortcode, attributes) {
749 var encodingRegexpRule = /embedpress(s?):\/\//;
750 applyShortcode = (typeof applyShortcode === 'undefined') ? true : applyShortcode;
751 attributes = (typeof attributes === 'undefined') ? {} : attributes;
752
753 var isEncoded = content.match(encodingRegexpRule);
754
755 // Restore http[s] in the url (converted to bypass autolink plugin)
756 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
757 content = content.replace('::__at__::', '@').trim();
758
759 if ('class' in attributes) {
760 var classesList = attributes.class.split(/\s/g);
761 var shouldRemoveDynamicWidthClass = false;
762 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
763 if (classesList[classIndex] === 'dynamic-width') {
764 shouldRemoveDynamicWidthClass = classIndex;
765 break;
766 }
767 }
768
769 if (shouldRemoveDynamicWidthClass !== false) {
770 classesList.splice(shouldRemoveDynamicWidthClass, 1);
771
772 if (classesList.length === 0) {
773 delete attributes.class;
774 }
775
776 attributes.class = classesList.join(' ');
777 }
778
779 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
780 }
781
782 if (isEncoded && applyShortcode) {
783 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE;
784 if (!!Object.keys(attributes).length) {
785 var attrValue;
786
787 for (var attrName in attributes) {
788 attrValue = attributes[attrName];
789
790 // Prevent `class` property being empty an treated as a boolean param
791 if (attrName.toLowerCase() === 'class' && !attrValue.length) {
792
793 } else {
794 if (attrValue.isBoolean()) {
795 shortcode += ' ';
796 if (attrValue.isFalse()) {
797 shortcode += '!';
798 }
799
800 shortcode += attrName;
801 } else {
802 shortcode += ' ' + attrName + '="' + attrValue + '"';
803 }
804 }
805 }
806 attrValue = attrName = null;
807 }
808
809 content = shortcode + ']' + content + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
810 }
811
812 return content;
813 };
814
815 /**
816 * Method executed after find the editor. It will make additional
817 * configurations and add the content's stylesheets for the preview
818 *
819 * @return void
820 */
821 self.onFindEditor = function (editorInstance) {
822 self.each = tinymce.each;
823 self.extend = tinymce.extend;
824 self.JSON = tinymce.util.JSON;
825 self.Node = tinymce.html.Node;
826
827 function onFindEditorCallback () {
828 $(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) + '">'));
829
830 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID, editorInstance, editorInstance);
831 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID, editorInstance, editorInstance);
832 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID, editorInstance, editorInstance);
833 self.addEvent('nodechange', editorInstance, self.onNodeChange);
834 self.addEvent('keydown', editorInstance, function (e) {
835 self.onKeyDown(e, editorInstance);
836 });
837
838 var onUndoCallback = function (e) {
839 self.onUndo(e, editorInstance);
840 };
841
842 self.addEvent('undo', editorInstance, onUndoCallback); // TinyMCE
843 self.addEvent('undo', editorInstance.undoManager, onUndoCallback); // JCE
844
845 var doc = editorInstance.getDoc();
846 $(doc).on('mouseenter', '.embedpress_wrapper', function (e) {
847 self.onMouseEnter(e, editorInstance);
848 });
849 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
850 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', function (e) {
851 self.cancelEvent(e, editorInstance);
852 });
853 doc = null;
854
855 // Add the node filter that will convert the url into the preview box for the embed code
856 editorInstance.parser.addNodeFilter('#text', function addNodeFilterIntoParser (nodes, arg) {
857 self.each(nodes, function eachNodeInParser (node) {
858 // Stop if the node is "isolated". It would generate an error in the browser console and break.
859 if (node.parent === null && node.prev === null) {
860 return;
861 }
862
863 var subject = node.value.trim();
864
865 if (!subject.isValidUrl()) {
866 if (!subject.match(SHORTCODE_REGEXP)) {
867 return;
868 }
869 }
870 subject = self.decodeEmbedURLSpecialChars(subject);
871 if (!subject.isValidUrl()) {
872 if (!subject.match(SHORTCODE_REGEXP)) {
873 return;
874 }
875 }
876
877 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
878
879 // These patterns need to have groups for the pre and post texts
880 // @TODO: maybe remove this list of URLs? Let the server side code decide what URL should be parsed
881 var patterns = self.getProvidersURLPatterns();
882
883 (function tryToMatchContentAgainstUrlPatternWithIndex (urlPatternIndex) {
884 if (urlPatternIndex < patterns.length) {
885 var urlPattern = patterns[urlPatternIndex];
886 var urlPatternRegex = new RegExp(urlPattern);
887
888 var url = self.decodeEmbedURLSpecialChars(subject).trim();
889
890 var matches = url.match(urlPatternRegex);
891 // Check if content matches the url pattern.
892 if (matches && matches !== null && !!matches.length) {
893 url = self.encodeEmbedURLSpecialChars(matches[2]);
894
895 var wrapper = self.addURLsPlaceholder(node, url, editorInstance);
896
897 setTimeout(function () {
898 var doc = editorInstance.getDoc();
899
900 if (doc === null) {
901 return;
902 }
903
904 var previewWrapper = $(doc.querySelector('#' + wrapper.attributes.map['id']));
905 var previewWrapperParent = $(previewWrapper.parent());
906
907 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === 'P') {
908 previewWrapperParent.replaceWith(previewWrapper);
909 }
910
911 var previewWrapperOlderSibling = previewWrapper.prev();
912 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === 'P' && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
913 previewWrapperOlderSibling.remove();
914 } else {
915 if (typeof previewWrapperOlderSibling.html() !== 'undefined') {
916 if (previewWrapperOlderSibling.html().match(/<[\/]?br>/)) {
917 if (!previewWrapperOlderSibling.prev().length) {
918 previewWrapperOlderSibling.remove();
919 }
920 }
921 }
922 }
923
924 var previewWrapperYoungerSibling = previewWrapper.next();
925 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === 'P') {
926 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
927 previewWrapperYoungerSibling.remove();
928 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
929 }
930 } else {
931 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
932 }
933
934 setTimeout(function () {
935 editorInstance.selection.select(editorInstance.getBody(), true);
936 editorInstance.selection.collapse(false);
937 }, 50);
938 }, 50);
939 } else {
940 // No match. So we move on to check the next url pattern.
941 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
942 }
943 }
944 })(0);
945 });
946 });
947
948 // Add the filter that will convert the preview box/embed code back to the raw url
949 editorInstance.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer (nodes, arg) {
950 self.each(nodes, function eachNodeInSerializer (node) {
951 // Stop if the node is "isolated". It would generate an error in the browser console and break.
952 if (node.parent === null && node.prev === null) {
953 return;
954 }
955
956 var nodeClasses = (node.attributes.map.class || '').split(' ');
957 var wrapperFactoryClasses = ['embedpress_wrapper', 'embedpress_placeholder', 'wpview', 'wpview-wrap'];
958
959 var isWrapped = nodeClasses.filter(function (n) {
960 return wrapperFactoryClasses.indexOf(n) != -1;
961 }).length > 0;
962
963 if (isWrapped) {
964 var factoryAttributes = ['id', 'style', 'data-loading-text', 'data-uid', 'data-url'];
965 var customAttributes = {};
966 var dataPrefix = 'data-';
967 for (var attr in node.attributes.map) {
968 if (attr.toLowerCase() !== 'class') {
969 if (factoryAttributes.indexOf(attr) < 0) {
970 // Remove the "data-" prefix for more readability
971 customAttributes[attr.replace(dataPrefix, '')] = node.attributes.map[attr];
972 }
973 } else {
974 var customClasses = [];
975 for (var wrapperClassIndex in nodeClasses) {
976 var wrapperClass = nodeClasses[wrapperClassIndex];
977 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
978 customClasses.push(wrapperClass);
979 }
980 }
981
982 if (!!customClasses.length) {
983 customAttributes.class = customClasses.join(' ');
984 }
985 }
986 }
987
988 var p = new self.Node('p', 1);
989
990 var text = new self.Node('#text', 3);
991 text.value = self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes);
992
993 p.append(text.clone());
994
995 node.replace(text);
996 text.replace(p);
997 }
998 });
999 });
1000
1001 editorInstance.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer (nodes, arg) {
1002 self.each(nodes, function eachNodeInSerializer (node) {
1003 if (node.firstChild == node.lastChild) {
1004 if (node.firstChild && 'value' in node.firstChild && (node.firstChild.value === '&nbsp;' || !node.firstChild.value.trim().length)) {
1005 node.remove();
1006 }
1007 }
1008 });
1009 });
1010
1011 //@todo:isthiseachreallynecessary?
1012 // Add event to reconfigure wrappers every time the content is loaded
1013 tinymce.each(tinymce.editors, function onEachEditor (editor) {
1014 self.addEvent('loadContent', editor, function onInitEditor (ed) {
1015 self.configureWrappers(editor);
1016 });
1017 });
1018
1019 // Add the edit form
1020
1021 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
1022 window.setTimeout(
1023 function afterTimeoutOnFindEditor () {
1024 /*
1025 * This is required because after load/refresh the page, the
1026 * onLoadContent is not being triggered automatically, so
1027 * we force the event
1028 */
1029 editorInstance.load();
1030 },
1031 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1032 500
1033 );
1034 }
1035
1036 // Let's make sure the inner doc has been fully loaded first.
1037 var checkTimesLimit = 100;
1038 var checkIndex = 0;
1039 var statusCheckerInterval = setInterval(function () {
1040 if (checkIndex === checkTimesLimit) {
1041 clearInterval(statusCheckerInterval);
1042 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1043 } else {
1044 var doc = editorInstance.getDoc();
1045 if (doc) {
1046 clearInterval(statusCheckerInterval);
1047 onFindEditorCallback();
1048 } else {
1049 checkIndex++;
1050 }
1051 }
1052 }, 250);
1053 };
1054
1055 self.fixIframeSize = function (iframe) {
1056 var maxWidth = 480;
1057 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1058 var ratio = $(iframe).height() / $(iframe).width();
1059 $(iframe).width(maxWidth);
1060 $(iframe).height(maxWidth * ratio);
1061 $(iframe).css('max-width', maxWidth);
1062 $(iframe).attr('max-width', maxWidth);
1063
1064 $(iframe).data('size-fixed', true);
1065 }
1066 };
1067
1068 /**
1069 * Function triggered on mouse enter the wrapper
1070 *
1071 * @param object e The event
1072 * @return void
1073 */
1074 self.onMouseEnter = function (e, editorInstance) {
1075 self.displayPreviewControllerPanel($(e.currentTarget), editorInstance);
1076 };
1077
1078 /**
1079 * Function triggered on mouse get out of the wrapper
1080 *
1081 * @param object e The event
1082 * @return void
1083 */
1084 self.onMouseOut = function (e) {
1085 // Check if the destiny is not a child element
1086 // Chrome
1087 if (self.isDefined(e.toElement)) {
1088 if (e.toElement.parentElement == e.fromElement
1089 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1090 ) {
1091 return false;
1092 }
1093 }
1094
1095 // Firefox
1096 if (self.isDefined(e.relatedTarget)) {
1097 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1098 return false;
1099 }
1100 }
1101
1102 self.hidePreviewControllerPanel();
1103 };
1104
1105 /**
1106 * Callback triggered by paste events. This should be hooked by TinyMCE's paste_preprocess
1107 * setting. A normal bind to the onPaste event doesn't work correctly all the times
1108 * (specially when you copy and paste content from the same editor).
1109 *
1110 * @param mixed - plugin
1111 * @param mixed - args
1112 *
1113 * @return void
1114 */
1115
1116 self.onPaste = function (plugin, args) {
1117 var urlPatternRegex = new RegExp(/(https?):\/\/([w]{3}\.)?.+?(?:\s|$)/i);
1118 var urlPatternsList = self.getProvidersURLPatterns();
1119
1120 // Split the pasted content into separated lines.
1121 var contentLines = args.content.split(/\n/g) || [];
1122 contentLines = contentLines.map(function (line, itemIndex) {
1123 // Check if there's a url into `line`.
1124 if (line.match(urlPatternRegex)) {
1125 // Split the current line across its space-characters to isolate the url.
1126 let termsList = line.trim().split(/\s+/);
1127 termsList = termsList.map(function (term, termIndex) {
1128 // Check if the term into the current line is a url.
1129 var match = term.match(urlPatternRegex);
1130 if (match) {
1131 for (var urlPatternIndex = 0; urlPatternIndex < urlPatternsList.length; urlPatternIndex++) {
1132 // Isolates that url from the rest of the content if the service is supported.
1133 var urlPattern = new RegExp(urlPatternsList[urlPatternIndex]);
1134 if (urlPattern.test(term)) {
1135 return '</p><p>' + match[0] + '</p><p>';
1136 }
1137 }
1138 }
1139
1140 return term;
1141 });
1142
1143 termsList[termsList.length - 1] = termsList[termsList.length - 1] + '<br>';
1144
1145 line = termsList.join(' ');
1146 }
1147
1148 return line;
1149 });
1150
1151 // Check if the text was transformed or not. If it was, add wrappers
1152 var content = contentLines.join('');
1153
1154 if (content.replace(/<br>$/, '') !== args.content) {
1155 args.content = '<p>' + args.content + '</p>';
1156 }
1157 };
1158
1159 /**
1160 * Method trigered on every node change, to detect new lines. It will
1161 * try to fix a default behavior for some editors of clone the parent
1162 * element when adding a line break. This will clone the embed wrapper
1163 * if we set the cursor after a preview wrapper and hit enter.
1164 *
1165 * @param object e The event
1166 * @return void
1167 */
1168 self.onNodeChange = function (e) {
1169 // Fix the clone parent on break lines issue
1170 // Check if a line break was added
1171 if (e.element.tagName === 'BR') {
1172 // Check one of the parent elements is a clonned embed wrapper
1173 if (e.parents.length > 0) {
1174 $.each(e.parents, function (index, parent) {
1175 if ($(parent).hasClass('embedpress_wrapper')) {
1176 // Remove the cloned wrapper and replace with a 'br' tag
1177 $(parent).replaceWith($('<br>'));
1178 }
1179 });
1180 }
1181 } else if (e.element.tagName === 'IFRAME') {
1182 if (e.parents.length > 0) {
1183 $.each(e.parents, function (index, parent) {
1184 parent = $(parent);
1185 if (parent.hasClass('embedpress_wrapper')) {
1186 var wrapper = $('.embedpress-wrapper', parent);
1187 if (wrapper.length > 1) {
1188 wrapper.get(0).remove();
1189 }
1190 }
1191 });
1192 }
1193 }
1194 };
1195
1196 self.onKeyDown = function (e, editorInstance) {
1197 var node = editorInstance.selection.getNode();
1198
1199 if (e.keyCode == 8 || e.keyCode == 46) {
1200 if (node.nodeName.toLowerCase() === 'p') {
1201 var children = $(node).children();
1202 if (children.length > 0) {
1203 $.each(children, function () {
1204 // On delete, make sure to remove the wrapper and children, not only the wrapper
1205 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1206 $(this).remove();
1207
1208 editorInstance.focus();
1209 }
1210 });
1211 }
1212 }
1213 } else {
1214 // Ignore the arrows keys
1215 var arrowsKeyCodes = [37, 38, 39, 40];
1216 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1217
1218 // Check if we are inside a preview wrapper
1219 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1220 // Avoid delete the wrapper or block line break if we are inside the wrapper
1221 if (e.keyCode == 13) {
1222 wrapper = $(self.getWrapperFromChild(node));
1223 if (wrapper.length > 0) {
1224 // Creates a temporary element which will be inserted after the wrapper
1225 var tmpId = '__embedpress__tmp_' + self.makeId();
1226 wrapper.after($('<span id="' + tmpId + '"></span>'));
1227 // Get and select the temporary element
1228 var span = editorInstance.dom.select('span#' + tmpId)[0];
1229 editorInstance.selection.select(span);
1230 // Remove the temporary element
1231 $(span).remove();
1232 }
1233
1234 return true;
1235 } else {
1236 // If we are inside the embed preview, ignore any key to avoid edition
1237 return self.cancelEvent(e, editorInstance);
1238 }
1239 }
1240 }
1241 }
1242
1243 return true;
1244 };
1245
1246 self.getWrapperFromChild = function (element) {
1247 // Is the wrapper
1248 if ($(element).hasClass('embedpress_wrapper')) {
1249 return element;
1250 } else {
1251 var $parent = $(element).parent();
1252
1253 if ($parent.length > 0) {
1254 return self.getWrapperFromChild($parent[0]);
1255 }
1256 }
1257
1258 return false;
1259 };
1260
1261 self.onUndo = function (e, editorInstance) {
1262 // Force re-render everything
1263 editorInstance.load();
1264 };
1265
1266 self.cancelEvent = function (e, editorInstance) {
1267 e.preventDefault();
1268 e.stopPropagation();
1269 editorInstance.dom.events.cancel();
1270
1271 return false;
1272 };
1273
1274 /**
1275 * Method executed when the edit button is clicked. It will display
1276 * a field with the current url, to update the current embed's source
1277 * url.
1278 *
1279 * @param Object e The event
1280 * @return void
1281 */
1282 self.onClickEditButton = function (e, editorInstance) {
1283 // Prevent edition of the panel
1284 self.cancelEvent(e, editorInstance);
1285
1286 self.activeWrapperForModal = self.activeWrapper;
1287
1288 var $wrapper = self.activeWrapperForModal;
1289 var wrapperUid = $wrapper.prop('id').replace('embedpress_wrapper_', '');
1290
1291 var customAttributes = {};
1292
1293 var $embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1294 var embedItem = $('iframe', $wrapper);
1295 if (!embedItem.length) {
1296 embedItem = null;
1297 }
1298
1299 $.each($embedInnerWrapper[0].attributes, function () {
1300 if (this.specified) {
1301 if (this.name !== 'class') {
1302 customAttributes[this.name.replace('data-', '').toLowerCase()] = this.value;
1303 }
1304 }
1305 });
1306
1307 var embedWidth = (((embedItem && embedItem.width()) || $embedInnerWrapper.data('width')) || $embedInnerWrapper.width()) || '';
1308 var embedHeight = (((embedItem && embedItem.height()) || $embedInnerWrapper.data('height')) || $embedInnerWrapper.height()) || '';
1309
1310 embedItem = $embedInnerWrapper = null;
1311
1312 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1313
1314 setTimeout(function () {
1315 $.ajax({
1316 type: 'GET',
1317 url: self.params.baseUrl + 'wp-admin/admin-ajax.php',
1318 data: {
1319 action: 'embedpress_get_embed_url_info',
1320 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1321 },
1322 beforeSend: function (request, requestSettings) {
1323 $('.loader-indicator', $wrapper).addClass('is-loading');
1324 },
1325 success: function (response) {
1326 if (!response) {
1327 bootbox.alert('Unable to get a valid response from the server.');
1328 return;
1329 }
1330 if (response.canBeResponsive) {
1331 var embedShouldBeResponsive = true;
1332 if ('width' in customAttributes || 'height' in customAttributes) {
1333 embedShouldBeResponsive = false;
1334 } else if ('responsive' in customAttributes && customAttributes['responsive'].isFalse()) {
1335 embedShouldBeResponsive = false;
1336 }
1337 }
1338
1339 bootbox.dialog({
1340 className: 'embedpress-modal',
1341 title: 'Editing Embed properties',
1342 message: '<form id="form-' + wrapperUid + '" embedpress>' +
1343 '<div class="row">' +
1344 '<div class="col-md-12">' +
1345 '<div class="form-group">' +
1346 '<label for="input-url-' + wrapperUid + '">Url</label>' +
1347 '<input class="form-control" type="url" id="input-url-' + wrapperUid + '" value="' + self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) + '">' +
1348 '</div>' +
1349 '</div>' +
1350 '</div>' +
1351 '<div class="row">' +
1352 (response.canBeResponsive ?
1353 '<div class="col-md-12">' +
1354 '<label>Responsive</label>' +
1355 '<div class="form-group">' +
1356 '<label class="radio-inline">' +
1357 '<input type="radio" name="input-responsive-' + wrapperUid + '" id="input-responsive-1-' + wrapperUid + '" value="1"' + (embedShouldBeResponsive ? ' checked="checked"' : '') + '> Yes' +
1358 '</label>' +
1359 '<label class="radio-inline">' +
1360 '<input type="radio" name="input-responsive-' + wrapperUid + '" id="input-responsive-0-' + wrapperUid + '" value="0"' + (!embedShouldBeResponsive ? ' checked="checked"' : '') + '> No' +
1361 '</label>' +
1362 '</div>' +
1363 '</div>' : '') +
1364 '<div class="col-md-6">' +
1365 '<div class="form-group">' +
1366 '<label for="input-width-' + wrapperUid + '">Width</label>' +
1367 '<input class="form-control" type="integer" id="input-width-' + wrapperUid + '" value="' + embedWidth + '"' + (embedShouldBeResponsive ? ' disabled' : '') + '>' +
1368 '</div>' +
1369 '</div>' +
1370 '<div class="col-md-6">' +
1371 '<div class="form-group">' +
1372 '<label for="input-height-' + wrapperUid + '">Height</label>' +
1373 '<input class="form-control" type="integer" id="input-height-' + wrapperUid + '" value="' + embedHeight + '"' + (embedShouldBeResponsive ? ' disabled' : '') + '>' +
1374 '</div>' +
1375 '</div>' +
1376 '</div>' +
1377 '</form>',
1378 buttons: {
1379 danger: {
1380 label: 'Cancel',
1381 className: 'btn-default',
1382 callback: function () {
1383 // do nothing
1384 self.activeWrapperForModal = null;
1385 }
1386 },
1387 success: {
1388 label: 'Save',
1389 className: 'btn-primary',
1390 callback: function () {
1391 var $wrapper = self.activeWrapperForModal;
1392
1393 // Select the current wrapper as a base for the new element
1394 editorInstance.focus();
1395 editorInstance.selection.select($wrapper[0]);
1396
1397 $wrapper.children().remove();
1398 $wrapper.remove();
1399
1400 if (response.canBeResponsive) {
1401 if ($('#form-' + wrapperUid + ' input[name="input-responsive-' + wrapperUid + '"]:checked').val().isFalse()) {
1402 var embedCustomWidth = $('#input-width-' + wrapperUid).val();
1403 if (parseInt(embedCustomWidth) > 0) {
1404 customAttributes['width'] = embedCustomWidth;
1405 }
1406
1407 var embedCustomHeight = $('#input-height-' + wrapperUid).val();
1408 if (parseInt(embedCustomHeight) > 0) {
1409 customAttributes['height'] = embedCustomHeight;
1410 }
1411
1412 customAttributes['responsive'] = 'false';
1413 } else {
1414 delete customAttributes['width'];
1415 delete customAttributes['height'];
1416
1417 customAttributes['responsive'] = 'true';
1418 }
1419 } else {
1420 var embedCustomWidth = $('#input-width-' + wrapperUid).val();
1421 if (parseInt(embedCustomWidth) > 0) {
1422 customAttributes['width'] = embedCustomWidth;
1423 }
1424
1425 var embedCustomHeight = $('#input-height-' + wrapperUid).val();
1426 if (parseInt(embedCustomHeight) > 0) {
1427 customAttributes['height'] = embedCustomHeight;
1428 }
1429 }
1430
1431 var customAttributesList = [];
1432 if (!!Object.keys(customAttributes).length) {
1433 for (var attrName in customAttributes) {
1434 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1435 }
1436 }
1437
1438 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? ' ' + customAttributesList.join(' ') : '') + ']' + $('#input-url-' + wrapperUid).val() + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
1439 // We do not directly replace the node because it was causing a bug on a second edit attempt
1440 editorInstance.execCommand('mceInsertContent', false, shortcode);
1441
1442 self.configureWrappers(editorInstance);
1443 }
1444 }
1445 }
1446 });
1447
1448 $('form[embedpress]').on('change', 'input[type="radio"]', function (e) {
1449 var self = $(this);
1450 var form = self.parents('form[embedpress]');
1451
1452 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
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($data.previewSettings);
1655 });
1656 })(jQuery, String, $data);
1657