PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 1.6.1
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v1.6.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 9 years ago index.html 9 years ago preview.js 9 years ago
preview.js
1895 lines
1 /**
2 * @package EmbedPress
3 * @author PressShack <help@pressshack.com>
4 * @copyright Copyright (C) 2017 Open Source Training, LLC. 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?|ftp|embedpresss?):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
18
19 return rule.test(this.toString());
20 }
21
22 String.prototype.hasShortcode = function(shortcode) {
23 var shortcodeRule = new RegExp('\\['+ shortcode +'(?:\\]|.+?\\])', "ig");
24 return !!this.toString().match(shortcodeRule);
25 }
26
27 String.prototype.stripShortcode = function(shortcode) {
28 var stripRule = new RegExp('(\\['+ shortcode +'(?:\\]|.+?\\])|\\[\\/'+ shortcode +'\\])', "ig");
29 return this.toString().replace(stripRule, "");
30 }
31
32 String.prototype.setShortcodeAttribute = function(attr, value, shortcode, replaceInsteadOfMerge) {
33 replaceInsteadOfMerge = typeof replaceInsteadOfMerge === "undefined" ? false : replaceInsteadOfMerge;
34 var subject = this.toString();
35
36 if (subject.hasShortcode(shortcode)) {
37 var attributes = subject.getShortcodeAttributes(shortcode);
38
39 if (attributes.hasOwnProperty(attr)) {
40 if (replaceInsteadOfMerge) {
41 attributes[attr] = value;
42 } else {
43 attributes[attr] += " " + value;
44 }
45 } else {
46 attributes[attr] = value;
47 }
48
49 if (!!Object.keys(attributes).length) {
50 var parsedAttributes = [];
51 for (var attr in attributes) {
52 parsedAttributes.push(attr + '="' + attributes[attr] + '"');
53 }
54
55 subject = '[' + shortcode + ' ' + parsedAttributes.join(" ") + ']' + subject.stripShortcode(shortcode) + '[/' + shortcode + ']';
56 } else {
57 subject = '[' + shortcode + ']' + subject.stripShortcode(shortcode) + '[/' + shortcode + ']';
58 }
59
60 return subject;
61 } else {
62 return subject;
63 }
64 }
65
66 String.prototype.getShortcodeAttributes = function(shortcode) {
67 var subject = this.toString();
68 if (subject.hasShortcode(shortcode)) {
69 var attributes = {};
70 var propertiesString = (new RegExp(/\[embed\s*(.*?)\]/ig)).exec(subject)[1]; // Separate all shortcode attributes from the rest of the string
71 if (propertiesString.length > 0) {
72 var extractAttributesRule = new RegExp(/(\!?\w+-?\w*)(?:="(.+?)")?/ig); // Extract attributes and their values
73 var match;
74 while (match = extractAttributesRule.exec(propertiesString)) {
75 var attrName = match[1];
76 var attrValue;
77 if (match[2] === undefined) {
78 // Prevent `class` property being empty an treated as a boolean param
79 if (attrName.toLowerCase() !== "class") {
80 if (attrName.indexOf('!') === 0) {
81 attrName = attrName.replace('!', "");
82 attrValue = "false";
83 } else {
84 attrValue = "true";
85 }
86
87 attributes[attrName] = attrValue;
88 }
89 } else {
90 attrValue = match[2];
91 if (attrValue.isBoolean()) {
92 attrValue = attrValue.isFalse() ? "false" : "true";
93 }
94
95 attributes[attrName] = attrValue;
96 }
97 }
98 match = extractAttributesRule = null;
99 }
100 propertiesString = null;
101
102 return attributes;
103 } else {
104 return {};
105 }
106 }
107
108 String.prototype.isBoolean = function() {
109 var subject = this.toString().trim().toLowerCase();
110
111 return subject.isTrue(false) || subject.isFalse();
112 };
113
114 String.prototype.isTrue = function(defaultValue) {
115 var subject = this.toString().trim().toLowerCase();
116 defaultValue = typeof defaultValue === undefined ? true : defaultValue;
117
118 switch (subject) {
119 case "":
120 defaultValue += "";
121 return !defaultValue.isFalse();
122 case "1":
123 case "true":
124 case "on":
125 case "yes":
126 case "y":
127 return true;
128 default:
129 return false;
130 }
131 };
132
133 String.prototype.isFalse = function() {
134 var subject = this.toString().trim().toLowerCase();
135
136 switch (subject) {
137 case "0":
138 case "false":
139 case "off":
140 case "no":
141 case "n":
142 case "nil":
143 case "null":
144 return true;
145 default:
146 return false;
147 }
148 };
149
150 var SHORTCODE_REGEXP = new RegExp('\\[\/?'+ $data.EMBEDPRESS_SHORTCODE +'\\]', "gi");
151
152 var EmbedPress = function() {
153 var self = this;
154
155 var PLG_SYSTEM_ASSETS_CSS_PATH = $data.EMBEDPRESS_URL_ASSETS +"css";
156 var PLG_CONTENT_ASSETS_CSS_PATH = PLG_SYSTEM_ASSETS_CSS_PATH;
157
158 /**
159 * The default params
160 *
161 * @type Object
162 */
163 self.params = {
164 baseUrl : '',
165 versionUID: '0'
166 };
167
168 /**
169 * True, if user agent is iOS
170 * @type Boolean True, if is iOS
171 */
172 self.iOS = /iPad|iPod|iPhone/.test(window.navigator.userAgent);
173
174 /**
175 * The active wrapper, activated by the mouse enter event
176 * @type Element
177 */
178 self.activeWrapper = null;
179
180 self.activeWrapperForModal = null;
181
182 /**
183 * The active controller panel
184 * @type Element
185 */
186 self.activeControllerPanel = null;
187
188 /**
189 * A list containing all loaded editor instances on the page
190 * @type Array
191 */
192 self.loadedEditors = [];
193
194 /**
195 * Init the plugin
196 *
197 * @param object params Override the plugin's params
198 * @return void
199 */
200 self.init = function (params) {
201 $.extend(self.params, params);
202
203 // Fix iOS doesn't firing click events on 'standard' elements
204 if (self.iOS) {
205 $(window.document.body).css('cursor', 'pointer');
206 }
207
208 $(self.onReady);
209 };
210
211 self.addEvent = function(event, element, callback) {
212 if (typeof element.on !== 'undefined') {
213 element.on(event, callback);
214 } else {
215 if (element['on' + event.capitalizeFirstLetter()]) {
216 element['on' + event.capitalizeFirstLetter()].add(callback);
217 }
218 }
219 };
220
221 self.isEmpty = function(list) {
222 return list.length === 0;
223 };
224
225 self.isDefined = function(attribute) {
226 return (typeof attribute !== 'undefined') && (attribute !== null);
227 }
228
229 self.makeId = function() {
230 var text = "";
231 var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
232
233 for( var i=0; i < 5; i++ )
234 text += possible.charAt(Math.floor(Math.random() * possible.length));
235
236 return text;
237 };
238
239 self.loadAsyncDynamicJsCodeFromElement = function(subject, wrapper, editorInstance)
240 {
241 subject = $(subject);
242 if (subject.prop('tagName').toLowerCase() === "script") {
243 var scriptSrc = subject.attr('src') || null;
244 if (!scriptSrc) {
245 self.addScriptDeclaration(wrapper, subject.html(), editorInstance);
246 } else {
247 self.addScript(scriptSrc, null, wrapper, editorInstance);
248 }
249 } else {
250 var innerScriptsList = $('script', subject);
251 if (innerScriptsList.length > 0) {
252 $.each(innerScriptsList, function(innerScriptIndex, innerScript) {
253 self.loadAsyncDynamicJsCodeFromElement(innerScript, wrapper, editorInstance);
254 });
255 }
256 }
257 }
258
259 /**
260 * Method executed on the document ready event
261 *
262 * @return void
263 */
264 self.onReady = function() {
265 if (self.tinymceIsAvailable()) {
266 // Wait until the editor is available
267 var interval = window.setInterval(
268 function() {
269 var editorsFound = self.getEditors();
270 if (editorsFound.length) {
271 self.loadedEditors = editorsFound;
272
273 for (var editorIndex = 0; editorIndex < self.loadedEditors.length; editorIndex++) {
274 self.onFindEditor(self.loadedEditors[editorIndex]);
275 }
276
277 window.clearInterval(interval);
278
279 return self.loadedEditors;
280 }
281 },
282 250
283 );
284 }
285 };
286
287 /**
288 * Detects if tinymce object is available
289 * @return Boolean True, if available
290 */
291 self.tinymceIsAvailable = function() {
292 return typeof window.tinymce === 'object' || typeof window.tinyMCE === "object";
293 }
294
295 /**
296 * Returns true if the controller panel is active
297 * @return Boolean True, if the controller panel is active
298 */
299 self.controllerPanelIsActive = function() {
300 return typeof self.activeControllerPanel !== 'undefined' && self.activeControllerPanel !== null;
301 };
302
303 /**
304 * Returns the editor
305 * @return Object The editor
306 */
307 self.getEditors = function() {
308 if (!window.tinymce || !window.tinymce.editors || window.tinymce.editors.length === 0) {
309 return [];
310 }
311
312 return window.tinymce.editors || [];
313 };
314
315 /**
316 * Parses the content, sending it to the component which will
317 * look for urls to be parsed into embed codes
318 *
319 * @param string content The content
320 * @param function onsuccess The callback called on success
321 * @return void
322 */
323 self.getParsedContent = function(content, onsuccess) {
324 // Get the parsed content
325 $.ajax({
326 type: 'POST',
327 url: self.params.baseUrl +"wp-admin/admin-ajax.php",
328 data: {
329 action: "embedpress_do_ajax_request",
330 subject: content
331 },
332 success: onsuccess,
333 dataType: 'json',
334 async: true
335 });
336 };
337
338 self.addStylesheet = function(url, editorInstance) {
339 var head = editorInstance.getDoc().getElementsByTagName('head')[0];
340
341 var $style = $('<link rel="stylesheet" type="text/css" href="' + url + '">');
342 $style.appendTo(head);
343 }
344
345 self.convertURLSchemeToPattern = function(scheme) {
346 var prefix = '(.*)((?:http|embedpress)s?:\\/\\/(?:www\\.)?',
347 suffix = '[\\/]?)(.*)',
348 pattern;
349
350 scheme = scheme.replace(/\*/g, '[a-zA-Z0-9=&_\\-\\?\\.\\/!\\+%:@,#]+');
351 scheme = scheme.replace(/\./g, '\\.');
352 scheme = scheme.replace(/\//g, '\\/');
353
354 return prefix + scheme + suffix;
355 };
356
357 self.getProvidersURLPatterns = function() {
358 // @todo: Add option to disable/enable the providers
359 var urlSchemes = [
360 // PollDaddy
361 '*.polldaddy.com/s/*',
362 '*.polldaddy.com/poll/*',
363 '*.polldaddy.com/ratings/*',
364 'polldaddy.com/s/*',
365 'polldaddy.com/poll/*',
366 'polldaddy.com/ratings/*',
367
368 // VideoPress
369 'videopress.com/v/*',
370
371 // Tumblr
372 '*.tumblr.com/post/*',
373
374 // SmugMug
375 'smugmug.com/*',
376 '*.smugmug.com/*',
377
378 // SlideShare
379 'slideshare.net/*/*',
380 '*.slideshare.net/*/*',
381
382 // Reddit
383 'reddit.com/r/[^/]+/comments/*',
384
385 // Photobucket
386 'i*.photobucket.com/albums/*',
387 'gi*.photobucket.com/groups/*',
388
389 // Cloudup
390 'cloudup.com/*',
391
392 // Imgur
393 'imgur.com/*',
394 'i.imgur.com/*',
395
396 // YouTube (http://www.youtube.com/)
397 'youtube.com/watch\\?*',
398
399 // Flickr (http://www.flickr.com/)
400 'flickr.com/photos/*/*',
401 'flic.kr/p/*',
402
403 // Viddler (http://www.viddler.com/)
404 'viddler.com/v/*',
405
406 // Hulu (http://www.hulu.com/)
407 'hulu.com/watch/*',
408
409 // Vimeo (http://vimeo.com/)
410 'vimeo.com/*',
411 'vimeo.com/groups/*/videos/*',
412
413 // CollegeHumor (http://www.collegehumor.com/)
414 'collegehumor.com/video/*',
415
416 // Deviantart.com (http://www.deviantart.com)
417 '*.deviantart.com/art/*',
418 '*.deviantart.com/*#/d*',
419 'fav.me/*',
420 'sta.sh/*',
421
422 // SlideShare (http://www.slideshare.net/)
423
424 // chirbit.com (http://www.chirbit.com/)
425 'chirb.it/*',
426
427 // nfb.ca (http://www.nfb.ca/)
428 '*.nfb.ca/film/*',
429
430 // Scribd (http://www.scribd.com/)
431 'scribd.com/doc/*',
432
433 // Dotsub (http://dotsub.com/)
434 'dotsub.com/view/*',
435
436 // Animoto (http://animoto.com/)
437 'animoto.com/play/*',
438
439 // Rdio (http://rdio.com/)
440 '*.rdio.com/artist/*',
441 '*.rdio.com/people/*',
442
443 // MixCloud (http://mixcloud.com/)
444 'mixcloud.com/*/*/',
445
446 // FunnyOrDie (http://www.funnyordie.com/)
447 'funnyordie.com/videos/*',
448
449 // Ted (http://ted.com)
450 'ted.com/talks/*',
451
452 // Sapo Videos (http://videos.sapo.pt)
453 'videos.sapo.pt/*',
454
455 // Official FM (http://official.fm)
456 'official.fm/tracks/*',
457 'official.fm/playlists/*',
458
459 // HuffDuffer (http://huffduffer.com)
460 'huffduffer.com/*/*',
461
462 // Shoudio (http://shoudio.com)
463 'shoudio.com/*',
464 'shoud.io/*',
465
466 // Moby Picture (http://www.mobypicture.com)
467 'mobypicture.com/user/*/view/*',
468 'moby.to/*',
469
470 // 23HQ (http://www.23hq.com)
471 '23hq.com/*/photo/*',
472
473 // Cacoo (https://cacoo.com)
474 'cacoo.com/diagrams/*',
475
476 // Dipity (http://www.dipity.com)
477 'dipity.com/*/*/',
478
479 // Roomshare (http://roomshare.jp)
480 'roomshare.jp/post/*',
481 'roomshare.jp/en/post/*',
482
483 // Dailymotion (http://www.dailymotion.com)
484 'dailymotion.com/video/*',
485
486 // Crowd Ranking (http://crowdranking.com)
487 'c9ng.com/*/*',
488
489 // CircuitLab (https://www.circuitlab.com/)
490 'circuitlab.com/circuit/*',
491
492 // Coub (http://coub.com/)
493 'coub.com/view/*',
494 'coub.com/embed/*',
495
496 // SpeakerDeck (https://speakerdeck.com)
497 'speakerdeck.com/*/*',
498
499 // Instagram (https://instagram.com)
500 'instagram.com/p/*',
501 'instagr.am/p/*',
502
503 // SoundCloud (http://soundcloud.com/)
504 'soundcloud.com/*',
505
506 // Kickstarter (http://www.kickstarter.com)
507 'kickstarter.com/projects/*',
508
509 // Ustream (http://www.ustream.tv)
510 '*.ustream.tv/*',
511 '*.ustream.com/*',
512
513 // Daily Mile (http://www.dailymile.com)
514 'dailymile.com/people/*/entries/*',
515
516 // Sketchfab (http://sketchfab.com)
517 'sketchfab.com/models/*',
518 'sketchfab.com/*/folders/*',
519
520 // Meetup (http://www.meetup.com)
521 'meetup.com/*',
522 'meetu.ps/*',
523
524 // AudioSnaps (http://audiosnaps.com)
525 'audiosnaps.com/k/*',
526
527 // RapidEngage (https://rapidengage.com)
528 'rapidengage.com/s/*',
529
530 // Getty Images (http://www.gettyimages.com/)
531 'gty.im/*',
532 'gettyimages.com/detail/photo/*',
533
534 // amCharts Live Editor (http://live.amcharts.com/)
535 'live.amcharts.com/*',
536
537 // Infogram (https://infogr.am/)
538 'infogr.am/*',
539
540 // ChartBlocks (http://www.chartblocks.com/)
541 'public.chartblocks.com/c/*',
542
543 // ReleaseWire (http://www.releasewire.com/)
544 'rwire.com/*',
545
546 // ShortNote (https://www.shortnote.jp/)
547 'shortnote.jp/view/notes/*',
548
549 // EgliseInfo (http://egliseinfo.catholique.fr/)
550 'egliseinfo.catholique.fr/*',
551
552 // Silk (http://www.silk.co/)
553 '*.silk.co/explore/*',
554 '*.silk.co/s/embed/*',
555
556 // Twitter
557 'twitter.com/*/status/*',
558 'twitter.com/i/moments/*',
559 'twitter.com/*/timelines/*',
560
561 // http://bambuser.com
562 'bambuser.com/v/*',
563
564 // https://clyp.it
565 'clyp.it/*',
566
567 // https://gist.github.com
568 'gist.github.com/*/*',
569
570 // http://issuu.com
571 'issuu.com/*',
572
573 // https://portfolium.com
574 'portfolium.com/*',
575
576 // https://www.reverbnation.com
577 'reverbnation.com/*',
578
579 // http://rutube.ru
580 'rutube.ru/video/*',
581
582 // https://spotify.com/
583 'open.spotify.com/*',
584
585 // http://www.videojug.com
586 'videojug.com/*',
587
588 // https://vine.com
589 'vine.co/v/*',
590
591 // Facebook
592 'facebook.com/*',
593
594 // Google Shortened Url
595 'goo.gl/*',
596
597 // Google Maps
598 'google.com/*',
599 'google.com.*/*',
600 'maps.google.com/*',
601
602 // Google Docs
603 'docs.google.com/presentation/*',
604 'docs.google.com/document/*',
605 'docs.google.com/spreadsheets/*',
606 'docs.google.com/forms/*',
607 'docs.google.com/drawings/*',
608
609 // Twitch.tv
610 '*.twitch.tv/*',
611 'twitch.tv/*',
612
613 // Giphy
614 '*.giphy.com/gifs/*',
615 'giphy.com/gifs/*',
616 'i.giphy.com/*',
617 'gph.is/*'
618 ],
619 patterns = [];
620
621 self.each(urlSchemes, function convertEachURLSchemesToPattern(scheme) {
622 patterns.push(self.convertURLSchemeToPattern(scheme));
623 });
624
625 return patterns;
626 };
627
628 self.addScript = function(source, callback, wrapper, editorInstance) {
629 var doc = editorInstance.getDoc();
630
631 if (typeof wrapper === 'undefined' || !wrapper) {
632 wrapper = $(doc.getElementsByTagName('head')[0]);
633 }
634
635 var $script = $(doc.createElement('script'));
636 $script.attr('async', 1);
637
638 if (typeof callback === 'function') {
639 $script.ready(callback);
640 }
641
642 $script.attr('src', source);
643
644 wrapper.append($script);
645 };
646
647 self.addScriptDeclaration = function(wrapper, declaration, editorInstance) {
648 var doc = editorInstance.getDoc(),
649 $script = $(doc.createElement('script'));
650
651 $(wrapper).append($script);
652
653 $script.text(declaration);
654 };
655
656 self.addURLsPlaceholder = function(node, url, editorInstance) {
657 var uid = self.makeId();
658
659 var wrapperClasses = ["embedpress_wrapper", "embedpress_placeholder", "wpview", "wpview-wrap"];
660
661 var shortcodeAttributes = node.value.getShortcodeAttributes($data.EMBEDPRESS_SHORTCODE);
662 var customAttributes = shortcodeAttributes;
663
664 var customClasses = "";
665 if (!!Object.keys(shortcodeAttributes).length) {
666 var specialAttributes = ["class", "href", "data-href"];
667 // Iterates over each attribute of shortcodeAttributes to add the prefix "data-" if missing
668 var dataPrefix = "data-";
669 var prefixedShortcodeAttributes = [];
670 for (var attr in shortcodeAttributes) {
671 if (specialAttributes.indexOf(attr) === -1) {
672 if (attr.indexOf(dataPrefix) !== 0) {
673 prefixedShortcodeAttributes[dataPrefix + attr] = shortcodeAttributes[attr];
674 } else {
675 prefixedShortcodeAttributes[attr] = shortcodeAttributes[attr];
676 }
677 } else {
678 attr = attr.replace(dataPrefix, "");
679 if (attr === "class") {
680 wrapperClasses.push(shortcodeAttributes[attr]);
681 }
682 }
683 }
684
685 shortcodeAttributes = prefixedShortcodeAttributes;
686 prefixedShortcodeAttributes = dataPrefix = null;
687 }
688
689 if (("data-width" in shortcodeAttributes || "data-height" in shortcodeAttributes) && "data-responsive" in shortcodeAttributes) {
690 shortcodeAttributes['data-responsive'] = "false";
691 }
692
693 var wrapper = new self.Node('div', 1);
694 var wrapperSettings = {
695 'class' : Array.from(new Set(wrapperClasses)).join(" "),
696 'data-url' : url,
697 'data-uid' : uid,
698 'id' : 'embedpress_wrapper_' + uid,
699 'data-loading-text': 'Loading your embed...'
700 };
701
702 wrapperSettings = $.extend({}, wrapperSettings, shortcodeAttributes);
703
704 if (wrapperSettings.class.indexOf('is-loading') === -1) {
705 wrapperSettings.class += " is-loading";
706 }
707
708 wrapper.attr(wrapperSettings);
709
710 var panel = new self.Node('div', 1);
711 panel.attr({
712 'id' : 'embedpress_controller_panel_' + uid,
713 'class': 'embedpress_controller_panel embedpress_ignore_mouseout hidden'
714 });
715 wrapper.append(panel);
716
717 function createGhostNode(htmlTag, content) {
718 htmlTag = htmlTag || "span";
719 content = content || "&nbsp;";
720
721 var ghostNode = new self.Node(htmlTag, 1);
722 ghostNode.attr({
723 'class': "hidden"
724 });
725
726 var ghostText = new self.Node('#text', 3);
727 ghostText.value = content;
728 ghostNode.append(ghostText);
729
730 return ghostNode;
731 }
732
733 var editButton = new self.Node('div', 1);
734 editButton.attr({
735 'id' : 'embedpress_button_edit_' + uid,
736 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
737 });
738 var editButtonIcon = new self.Node('div', 1);
739 editButtonIcon.attr({
740 'class': 'embedpress-icon-pencil embedpress_ignore_mouseout'
741 });
742 editButtonIcon.append(createGhostNode());
743 editButton.append(editButtonIcon);
744 panel.append(editButton);
745
746 var removeButton = new self.Node('div', 1);
747 removeButton.attr({
748 'id' : 'embedpress_button_remove_' + uid,
749 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
750 });
751 var removeButtonIcon = new self.Node('div', 1);
752 removeButtonIcon.attr({
753 'class': 'embedpress-icon-x embedpress_ignore_mouseout'
754 });
755 removeButtonIcon.append(createGhostNode());
756 removeButton.append(removeButtonIcon);
757 panel.append(removeButton);
758
759 node.value = node.value.trim();
760
761 node.replace(wrapper);
762
763 // Trigger the timeout which will load the content
764 window.setTimeout(function() {
765 self.parseContentAsync(uid, url, customAttributes, editorInstance);
766 }, 200);
767
768 return wrapper;
769 };
770
771 self.parseContentAsync = function(uid, url, customAttributes, editorInstance) {
772 customAttributes = typeof customAttributes === "undefined" ? {} : customAttributes;
773
774 url = self.decodeEmbedURLSpecialChars(url, true, customAttributes);
775 var rawUrl = url.stripShortcode($data.EMBEDPRESS_SHORTCODE);
776
777 $(self).triggerHandler('EmbedPress.beforeEmbed', {
778 'url' : rawUrl,
779 'meta': {
780 'attributes': customAttributes || {}
781 }
782 });
783
784 // Get the parsed embed code from the EmbedPress plugin
785 self.getParsedContent(url, function getParsedContentCallback(result) {
786 var embeddedContent = (typeof result.data === "object" ? result.data.embed : result.data).stripShortcode($data.EMBEDPRESS_SHORTCODE);
787
788 var $wrapper = $(self.getElementInContentById('embedpress_wrapper_' + uid, editorInstance));
789 var wrapperParent = $($wrapper.parent());
790
791 // Check if $wrapper was rendered inside a <p> element.
792 if (wrapperParent.prop('tagName') && wrapperParent.prop('tagName').toUpperCase() === "P") {
793 wrapperParent.replaceWith($wrapper);
794 // Check if there's at least one "space" after $wrapper.
795 var nextSibling = $($wrapper).next();
796 if (!nextSibling.length || nextSibling.prop('tagName').toUpperCase() !== "P") {
797 //$('<p>&nbsp;</p>').insertAfter($wrapper);
798 }
799 nextSibling = null;
800 }
801 wrapperParent = null;
802
803 // Check if the url could not be embedded for some reason.
804 if (rawUrl === embeddedContent) {
805 // Echoes the raw url
806 $wrapper.replaceWith($('<p>'+ rawUrl +'</p>'));
807 return;
808 }
809
810 $wrapper.removeClass('is-loading');
811
812 // Parse as DOM element
813 var $content;
814 try {
815 $content = $(embeddedContent);
816 } catch(err) {
817 // Fallback to a div, if the result is not a html markup, e.g. a url
818 $content = $('<div>');
819 $content.html(embeddedContent);
820 }
821
822 if (!$('iframe', $content).length) {
823 var contentWrapper = $($content).clone();
824 contentWrapper.html('');
825
826 var dom = editorInstance.dom;
827
828 $wrapper.removeClass('embedpress_placeholder');
829
830 $wrapper.append(contentWrapper);
831
832 setTimeout(function() {
833 editorInstance.undoManager.transact(function() {
834 var iframe = editorInstance.getDoc().createElement('iframe');
835 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
836 iframe.frameBorder = '0';
837 iframe.allowTransparency = 'true';
838 iframe.scrolling = 'no';
839 iframe.class = "wpview-sandbox";
840 iframe.style.width = '100%';
841
842 dom.add(contentWrapper, iframe);
843 var iframeWindow = iframe.contentWindow;
844 // Content failed to load.
845 if (!iframeWindow) {
846 return;
847 }
848
849 var iframeDoc = iframeWindow.document;
850
851 $(iframe).load(function() {
852 var maximumChecksAllowed = 8;
853 var checkIndex = 0;
854
855 var checkerInterval = setInterval(function() {
856 if (checkIndex === maximumChecksAllowed) {
857 clearInterval(checkerInterval);
858
859 setTimeout(function() {
860 $wrapper.css('width', iframe.width);
861 $wrapper.css('height', iframe.height);
862 }, 100);
863 } else {
864 if (customAttributes.height) {
865 iframe.height = customAttributes.height;
866 iframe.style.height = customAttributes.height +'px';
867 } else {
868 iframe.height = $('body', iframeDoc).height();
869 }
870
871 if (customAttributes.width) {
872 iframe.width = customAttributes.width;
873 iframe.style.width = customAttributes.width +'px';
874 } else {
875 iframe.width = $('body', iframeDoc).width();
876 }
877
878 checkIndex++;
879 }
880 }, 250);
881 });
882
883 iframeDoc.open();
884 iframeDoc.write(
885 '<!DOCTYPE html>'+
886 '<html>'+
887 '<head>'+
888 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'+
889 '<style>'+
890 'html {'+
891 'background: transparent;'+
892 'padding: 0;'+
893 'margin: 0;'+
894 '}'+
895 'body#wpview-iframe-sandbox {'+
896 'background: transparent;'+
897 'padding: 1px 0 !important;'+
898 'margin: -1px 0 0 !important;'+
899 '}'+
900 'body#wpview-iframe-sandbox:before,'+
901 'body#wpview-iframe-sandbox:after {'+
902 'display: none;'+
903 'content: "";'+
904 '}'+
905 '</style>'+
906 '</head>'+
907 '<body id="wpview-iframe-sandbox" class="'+ editorInstance.getBody().className +'" style="display: inline-block;">'+
908 $content.html() +
909 '</body>'+
910 '</html>'
911 );
912 iframeDoc.close();
913 });
914 }, 50);
915 } else {
916 $wrapper.removeClass('embedpress_placeholder');
917
918 self.appendElementsIntoWrapper($content, $wrapper, editorInstance);
919 }
920
921 $wrapper.append($('<span class="wpview-end"></span>'));
922
923 if (result && result.data && typeof result.data === "object") {
924 result.data.width = $($wrapper).width();
925 result.data.height = $($wrapper).height();
926 }
927
928 $(self).triggerHandler('EmbedPress.afterEmbed', {
929 'meta' : result.data,
930 'url' : rawUrl,
931 'wrapper': $wrapper
932 });
933 });
934 };
935
936 self.appendElementsIntoWrapper = function(elementsList, wrapper, editorInstance) {
937 if (elementsList.length > 0) {
938 $.each(elementsList, function appendElementIntoWrapper(elementIndex, element) {
939 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
940 if (element.tagName.toLowerCase() !== 'script') {
941 wrapper.append($(element));
942
943 if (element.tagName.toLowerCase() === 'iframe') {
944 $(element).ready(function() {
945 window.setTimeout(function() {
946 $.each(editorInstance.dom.select('div.embedpress_wrapper iframe'), function(elementIndex, iframe) {
947 self.fixIframeSize(iframe);
948 });
949 }, 300);
950 });
951 } else if (element.tagName.toLowerCase() === "div") {
952 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
953 // This ensures that the embed wrapper have the same width as its content
954 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
955 }
956
957 $(element).css('max-width', $($(element).parents('body').get(0)).width());
958 }
959 }
960
961 self.loadAsyncDynamicJsCodeFromElement(element, wrapper, editorInstance);
962 });
963 }
964
965 return wrapper;
966 };
967
968 self.encodeEmbedURLSpecialChars = function(content) {
969 if (content.match(SHORTCODE_REGEXP)) {
970 var subject = content.replace(SHORTCODE_REGEXP, '');
971
972 if (!subject.isValidUrl()) {
973 return content;
974 }
975
976 content = subject;
977 subject = null;
978 }
979
980 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
981 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
982
983 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
984 content = content.replace('@', '::__at__::').trim();
985
986 return content;
987 };
988
989 self.decodeEmbedURLSpecialChars = function(content, applyShortcode, attributes) {
990 var encodingRegexpRule = /embedpress(s?):\/\//;
991 applyShortcode = (typeof applyShortcode === "undefined") ? true : applyShortcode;
992 attributes = (typeof attributes === "undefined") ? {} : attributes;
993
994 var isEncoded = content.match(encodingRegexpRule);
995
996 // Restore http[s] in the url (converted to bypass autolink plugin)
997 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
998 content = content.replace('::__at__::', '@').trim();
999
1000 if ("class" in attributes) {
1001 var classesList = attributes.class.split(/\s/g);
1002 var shouldRemoveDynamicWidthClass = false;
1003 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
1004 if (classesList[classIndex] === "dynamic-width") {
1005 shouldRemoveDynamicWidthClass = classIndex;
1006 break;
1007 }
1008 }
1009
1010 if (shouldRemoveDynamicWidthClass !== false) {
1011 classesList.splice(shouldRemoveDynamicWidthClass, 1);
1012
1013 if (classesList.length === 0) {
1014 delete attributes.class;
1015 }
1016
1017 attributes.class = classesList.join(" ");
1018 }
1019
1020 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
1021 }
1022
1023 if (isEncoded && applyShortcode) {
1024 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE;
1025 if (!!Object.keys(attributes).length) {
1026 var attrValue;
1027
1028 for (var attrName in attributes) {
1029 attrValue = attributes[attrName];
1030
1031 // Prevent `class` property being empty an treated as a boolean param
1032 if (attrName.toLowerCase() === "class" && !attrValue.length) {
1033 continue;
1034 }
1035 else {
1036 if (attrValue.isBoolean()) {
1037 shortcode += " ";
1038 if (attrValue.isFalse()) {
1039 shortcode += "!";
1040 }
1041
1042 shortcode += attrName
1043 } else {
1044 shortcode += ' '+ attrName +'="'+ attrValue +'"';
1045 }
1046 }
1047 }
1048 attrValue = attrName = null;
1049 }
1050
1051 content = shortcode + ']' + content + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
1052 }
1053
1054 return content;
1055 };
1056
1057 /**
1058 * Method executed after find the editor. It will make additional
1059 * configurations and add the content's stylesheets for the preview
1060 *
1061 * @return void
1062 */
1063 self.onFindEditor = function(editorInstance) {
1064 self.each = tinymce.each;
1065 self.extend = tinymce.extend;
1066 self.JSON = tinymce.util.JSON;
1067 self.Node = tinymce.html.Node;
1068
1069 function onFindEditorCallback() {
1070 $(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) + '">'));
1071
1072 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID, editorInstance, editorInstance);
1073 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID, editorInstance, editorInstance);
1074 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID, editorInstance, editorInstance);
1075 self.addEvent('paste', editorInstance, function(a, b) {
1076 self.onPaste(a, b, editorInstance);
1077 });
1078 self.addEvent('nodechange', editorInstance, self.onNodeChange);
1079 self.addEvent('keydown', editorInstance, function(e) {
1080 self.onKeyDown(e, editorInstance);
1081 });
1082
1083 var onUndoCallback = function(e) {
1084 self.onUndo(e, editorInstance);
1085 };
1086
1087 self.addEvent('undo', editorInstance, onUndoCallback); // TinyMCE
1088 self.addEvent('undo', editorInstance.undoManager, onUndoCallback); // JCE
1089
1090 var doc = editorInstance.getDoc();
1091 $(doc).on('mouseenter', '.embedpress_wrapper', function(e) {
1092 self.onMouseEnter(e, editorInstance);
1093 });
1094 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
1095 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', function(e) {
1096 self.cancelEvent(e, editorInstance)
1097 });
1098 doc = null;
1099 // Add the node filter that will convert the url into the preview box for the embed code
1100 // @todo: Recognize <a> tags as well
1101
1102 editorInstance.parser.addNodeFilter('#text', function addNodeFilterIntoParser(nodes, arg) {
1103 self.each(nodes, function eachNodeInParser(node) {
1104 var subject = node.value.trim();
1105 if (!subject.isValidUrl()) {
1106 if (!subject.match(SHORTCODE_REGEXP)) {
1107 return;
1108 }
1109 }
1110
1111 subject = self.decodeEmbedURLSpecialChars(subject);
1112 if (!subject.isValidUrl()) {
1113 if (!subject.match(SHORTCODE_REGEXP)) {
1114 return;
1115 }
1116 }
1117
1118 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
1119
1120 // These patterns need to have groups for the pre and post texts
1121 var patterns = self.getProvidersURLPatterns();
1122
1123 (function tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex) {
1124 if (urlPatternIndex < patterns.length) {
1125 var urlPattern = patterns[urlPatternIndex];
1126 var urlPatternRegex = new RegExp(urlPattern);
1127
1128 var url = self.decodeEmbedURLSpecialChars(subject).trim();
1129
1130 var matches = url.match(urlPatternRegex);
1131 // Check if content matches the url pattern.
1132 if (matches && matches !== null && !!matches.length) {
1133 url = self.encodeEmbedURLSpecialChars(matches[2]);
1134
1135 var wrapper = self.addURLsPlaceholder(node, url, editorInstance);
1136
1137 setTimeout(function() {
1138 var doc = editorInstance.getDoc();
1139
1140 var previewWrapper = $(doc.querySelector('#'+ wrapper.attributes.map['id']));
1141 var previewWrapperParent = $(previewWrapper.parent());
1142
1143 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === "P") {
1144 previewWrapperParent.replaceWith(previewWrapper);
1145 }
1146
1147 var previewWrapperOlderSibling = previewWrapper.prev();
1148 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === "P" && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
1149 previewWrapperOlderSibling.remove();
1150 } else {
1151 if (previewWrapperOlderSibling.html().match(/<[\/]?br>/)) {
1152 if (!previewWrapperOlderSibling.prev().length) {
1153 previewWrapperOlderSibling.remove();
1154 }
1155 }
1156 }
1157
1158 var previewWrapperYoungerSibling = previewWrapper.next();
1159 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === "P") {
1160 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
1161 previewWrapperYoungerSibling.remove();
1162 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
1163 }
1164 } else {
1165 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
1166 }
1167
1168 setTimeout(function() {
1169 editorInstance.selection.select(editorInstance.getBody(), true);
1170 editorInstance.selection.collapse(false);
1171 }, 50);
1172 }, 50);
1173 } else {
1174 // No match. So we move on to check the next url pattern.
1175 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
1176 }
1177 }
1178 })(0);
1179 });
1180 });
1181
1182 // Add the filter that will convert the preview box/embed code back to the raw url
1183 editorInstance.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer(nodes, arg) {
1184 self.each(nodes, function eachNodeInSerializer(node) {
1185 var nodeClasses = (node.attributes.map.class || "").split(' ');
1186 var wrapperFactoryClasses = ["embedpress_wrapper", "embedpress_placeholder", "wpview", "wpview-wrap"];
1187
1188 var isWrapped = nodeClasses.filter(function(n) {
1189 return wrapperFactoryClasses.indexOf(n) != -1;
1190 }).length > 0;
1191
1192 if (isWrapped) {
1193 var factoryAttributes = ["id", "style", "data-loading-text", "data-uid", "data-url"];
1194 var customAttributes = {};
1195 var dataPrefix = "data-";
1196 for (var attr in node.attributes.map) {
1197 if (attr.toLowerCase() !== "class") {
1198 if (factoryAttributes.indexOf(attr) < 0) {
1199 // Remove the "data-" prefix for more readability
1200 customAttributes[attr.replace(dataPrefix, "")] = node.attributes.map[attr];
1201 }
1202 } else {
1203 var customClasses = [];
1204 for (var wrapperClassIndex in nodeClasses) {
1205 var wrapperClass = nodeClasses[wrapperClassIndex];
1206 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
1207 customClasses.push(wrapperClass);
1208 }
1209 }
1210
1211 if (!!customClasses.length) {
1212 customAttributes.class = customClasses.join(" ");
1213 }
1214 }
1215 }
1216
1217 var p = new self.Node('p', 1);
1218
1219 var text = new self.Node('#text', 3);
1220 text.value = self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes);
1221
1222 p.append(text.clone());
1223
1224 node.replace(text);
1225 text.replace(p);
1226 }
1227 });
1228 });
1229
1230 editorInstance.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer(nodes, arg) {
1231 self.each(nodes, function eachNodeInSerializer(node) {
1232 if (node.firstChild == node.lastChild) {
1233 if (node.firstChild && "value" in node.firstChild && (node.firstChild.value === "&nbsp;" || !node.firstChild.value.trim().length)) {
1234 node.remove();
1235 }
1236 }
1237 });
1238 });
1239
1240 //@todo:isthiseachreallynecessary?
1241 // Add event to reconfigure wrappers every time the content is loaded
1242 tinymce.each(tinymce.editors, function onEachEditor(editor) {
1243 self.addEvent('loadContent', editor, function onInitEditor(ed) {
1244 self.configureWrappers(editor);
1245 });
1246 });
1247
1248 // Add the edit form
1249
1250 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
1251 window.setTimeout(
1252 function afterTimeoutOnFindEditor() {
1253 /*
1254 * This is required because after load/refresh the page, the
1255 * onLoadContent is not being triggered automatically, so
1256 * we force the event
1257 */
1258 editorInstance.load();
1259 },
1260 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1261 500
1262 );
1263 }
1264
1265 // Let's make sure the inner doc has been fully loaded first.
1266 var checkTimesLimit = 100;
1267 var checkIndex = 0;
1268 var statusCheckerInterval = setInterval(function() {
1269 if (checkIndex === checkTimesLimit) {
1270 clearInterval(statusCheckerInterval);
1271 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1272 } else {
1273 var doc = editorInstance.getDoc();
1274 if (doc) {
1275 clearInterval(statusCheckerInterval);
1276 onFindEditorCallback();
1277 } else {
1278 checkIndex++;
1279 }
1280 }
1281 }, 250);
1282 };
1283
1284 self.fixIframeSize = function(iframe) {
1285 var maxWidth = 480;
1286 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1287 var ratio = $(iframe).height() / $(iframe).width();
1288 $(iframe).width(maxWidth);
1289 $(iframe).height(maxWidth * ratio);
1290 $(iframe).css('max-width', maxWidth);
1291 $(iframe).attr('max-width', maxWidth);
1292
1293 $(iframe).data('size-fixed', true);
1294 }
1295 }
1296
1297 /**
1298 * Function triggered on mouse enter the wrapper
1299 *
1300 * @param object e The event
1301 * @return void
1302 */
1303 self.onMouseEnter = function(e, editorInstance) {
1304 self.displayPreviewControllerPanel($(e.currentTarget), editorInstance);
1305 };
1306
1307 /**
1308 * Function triggered on mouse get out of the wrapper
1309 *
1310 * @param object e The event
1311 * @return void
1312 */
1313 self.onMouseOut = function(e) {
1314 // Check if the destiny is not a child element
1315 // Chrome
1316 if (self.isDefined(e.toElement)) {
1317 if (e.toElement.parentElement == e.fromElement
1318 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1319 ) {
1320 return false;
1321 }
1322 }
1323
1324 // Firefox
1325 if (self.isDefined(e.relatedTarget)) {
1326 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1327 return false;
1328 }
1329 }
1330
1331 self.hidePreviewControllerPanel();
1332 };
1333
1334 /**
1335 * Callback triggered by paste events. Receives two arguments due to compatibility with JCE and TinyMCE that handles
1336 * this event slightly different from each other.
1337 *
1338 * @param mixed - Can be either the Editor or Event
1339 * @param mixed - Can be either the Editor or Event
1340 *
1341 * @return void
1342 */
1343
1344 self.onPaste = function(e, b, editorInstance) {
1345 var urlPatternRegex = new RegExp(/(https?):\/\/([w]{3}\.)?.+?(?:\s|$)/i);
1346
1347 var event = null;
1348 if (e.preventDefault) {
1349 event = e;
1350 } else {
1351 event = b;
1352 }
1353
1354 event.preventDefault();
1355
1356 var urlPatternsList = self.getProvidersURLPatterns();
1357
1358 // Check for clipboard data in various places for cross-browser compatibility an get its data as text.
1359 var rawContent = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');
1360
1361 // Split the pasted content into separated lines.
1362 var contentLines = rawContent.split(/\n/g) || [];
1363 contentLines = contentLines.map(function(line, itemIndex) {
1364 // Check if there's a url into `line`.
1365 if (line.match(urlPatternRegex)) {
1366 // Split the current line across its space-characters to isolate the url.
1367 let termsList = line.trim().split(/\s+/);
1368 termsList = termsList.map(function(term, termIndex) {
1369 // Check if the term into the current line is a url.
1370 var match = term.match(urlPatternRegex);
1371 if (match) {
1372 for (var urlPatternIndex = 0; urlPatternIndex < urlPatternsList.length; urlPatternIndex++) {
1373 // Isolates that url from the rest of the content if the service is supported.
1374 var urlPattern = new RegExp(urlPatternsList[urlPatternIndex]);
1375 if (urlPattern.test(term)) {
1376 return '</p><p>'+ match[0] +'</p><p>';
1377 }
1378 }
1379 }
1380
1381 return term;
1382 });
1383
1384 termsList[termsList.length - 1] = termsList[termsList.length - 1] + '<br>';
1385
1386 line = termsList.join(' ');
1387 }
1388
1389 return line;
1390 });
1391
1392 var content = '<p>'+ contentLines.join('') +'</p>';
1393
1394 // Insert the new content into the editor.
1395 editorInstance.execCommand('mceInsertContent', false, content);
1396 };
1397
1398 /**
1399 * Method trigered on every node change, to detect new lines. It will
1400 * try to fix a default behavior for some editors of clone the parent
1401 * element when adding a line break. This will clone the embed wrapper
1402 * if we set the cursor after a preview wrapper and hit enter.
1403 *
1404 * @param object e The event
1405 * @return void
1406 */
1407 self.onNodeChange = function(e) {
1408 // Fix the clone parent on break lines issue
1409 // Check if a line break was added
1410 if (e.element.tagName === 'BR') {
1411 // Check one of the parent elements is a clonned embed wrapper
1412 if (e.parents.length > 0) {
1413 $.each(e.parents, function(index, parent) {
1414 if ($(parent).hasClass('embedpress_wrapper')) {
1415 // Remove the cloned wrapper and replace with a 'br' tag
1416 $(parent).replaceWith($('<br>'));
1417 }
1418 });
1419 }
1420 } else if (e.element.tagName === "IFRAME") {
1421 if (e.parents.length > 0) {
1422 $.each(e.parents, function(index, parent) {
1423 parent = $(parent);
1424 if (parent.hasClass('embedpress_wrapper')) {
1425 var wrapper = $('.embedpress-wrapper', parent);
1426 if (wrapper.length > 1) {
1427 wrapper.get(0).remove();
1428 }
1429 }
1430 });
1431 }
1432 }
1433 };
1434
1435 self.onKeyDown = function(e, editorInstance) {
1436 var node = editorInstance.selection.getNode();
1437
1438 if (e.keyCode == 8 || e.keyCode == 46) {
1439 if (node.nodeName.toLowerCase() === 'p') {
1440 var children = $(node).children();
1441 if (children.length > 0) {
1442 $.each(children, function() {
1443 // On delete, make sure to remove the wrapper and children, not only the wrapper
1444 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1445 $(this).remove();
1446
1447 editorInstance.focus();
1448 }
1449 });
1450 }
1451 }
1452 } else {
1453 // Ignore the arrows keys
1454 var arrowsKeyCodes = [37, 38, 39, 40];
1455 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1456
1457 // Check if we are inside a preview wrapper
1458 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1459 // Avoid delete the wrapper or block line break if we are inside the wrapper
1460 if (e.keyCode == 13) {
1461 wrapper = $(self.getWrapperFromChild(node));
1462 if (wrapper.length > 0) {
1463 // Creates a temporary element which will be inserted after the wrapper
1464 var tmpId = '__embedpress__tmp_' + self.makeId();
1465 wrapper.after($('<span id="' + tmpId + '"></span>'));
1466 // Get and select the temporary element
1467 var span = editorInstance.dom.select('span#' + tmpId)[0];
1468 editorInstance.selection.select(span);
1469 // Remove the temporary element
1470 $(span).remove();
1471 }
1472
1473 return true;
1474 } else {
1475 // If we are inside the embed preview, ignore any key to avoid edition
1476 return self.cancelEvent(e, editorInstance);
1477 }
1478 }
1479 }
1480 }
1481
1482 return true;
1483 }
1484
1485 self.getWrapperFromChild = function(element) {
1486 // Is the wrapper
1487 if ($(element).hasClass('embedpress_wrapper')) {
1488 return element;
1489 } else {
1490 var $parent = $(element).parent();
1491
1492 if ($parent.length > 0) {
1493 return self.getWrapperFromChild($parent[0]);
1494 }
1495 }
1496
1497 return false;
1498 };
1499
1500 self.onUndo = function(e, editorInstance) {
1501 // Force re-render everything
1502 editorInstance.load();
1503 };
1504
1505 self.cancelEvent = function(e, editorInstance) {
1506 e.preventDefault();
1507 e.stopPropagation();
1508 editorInstance.dom.events.cancel();
1509
1510 return false;
1511 };
1512
1513 /**
1514 * Method executed when the edit button is clicked. It will display
1515 * a field with the current url, to update the current embed's source
1516 * url.
1517 *
1518 * @param Object e The event
1519 * @return void
1520 */
1521 self.onClickEditButton = function(e, editorInstance) {
1522 // Prevent edition of the panel
1523 self.cancelEvent(e, editorInstance);
1524
1525 self.activeWrapperForModal = self.activeWrapper;
1526
1527 var $wrapper = self.activeWrapperForModal;
1528 var wrapperUid = $wrapper.prop('id').replace("embedpress_wrapper_", "");
1529
1530 var customAttributes = {};
1531
1532 var embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1533 var embedItem = $('iframe', $wrapper);
1534 if (!embedItem.length) {
1535 embedItem = null;
1536 }
1537
1538 $.each(embedInnerWrapper.attributes, function() {
1539 if (this.specified) {
1540 if (this.name !== "class") {
1541 customAttributes[this.name.replace('data-', "").toLowerCase()] = this.value;
1542 }
1543 }
1544 });
1545
1546 var embedWidth = (((embedItem && embedItem.width()) || embedInnerWrapper.data('width')) || embedInnerWrapper.width()) || "";
1547 var embedHeight = (((embedItem && embedItem.height()) || embedInnerWrapper.data('height')) || embedInnerWrapper.height()) || "";
1548
1549 embedItem = embedInnerWrapper = null;
1550
1551 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1552
1553 setTimeout(function() {
1554 $.ajax({
1555 type: "GET",
1556 url: self.params.baseUrl +"wp-admin/admin-ajax.php",
1557 data: {
1558 action: "embedpress_get_embed_url_info",
1559 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1560 },
1561 beforeSend: function(request, requestSettings) {
1562 $('.loader-indicator', $wrapper).addClass('is-loading');
1563 },
1564 success: function(response) {
1565 if (!response) {
1566 bootbox.alert('Unable to get a valid response from the server.');
1567 return;
1568 }
1569
1570 if (response.canBeResponsive) {
1571 var embedShouldBeResponsive = true;
1572 if ("width" in customAttributes || "height" in customAttributes) {
1573 embedShouldBeResponsive = false;
1574 } else if ("responsive" in customAttributes && customAttributes['responsive'].isFalse()) {
1575 embedShouldBeResponsive = false;
1576 }
1577 }
1578
1579 bootbox.dialog({
1580 className: "embedpress-modal",
1581 title: "Editing Embed properties",
1582 message: '<form id="form-'+ wrapperUid +'" embedpress>'+
1583 '<div class="row">'+
1584 '<div class="col-md-12">'+
1585 '<div class="form-group">'+
1586 '<label for="input-url-'+ wrapperUid +'">Url</label>'+
1587 '<input class="form-control" type="url" id="input-url-'+ wrapperUid +'" value="'+ self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) +'">'+
1588 '</div>'+
1589 '</div>'+
1590 '</div>'+
1591 '<div class="row">'+
1592 (response.canBeResponsive ?
1593 '<div class="col-md-12">'+
1594 '<label>Responsive</label>'+
1595 '<div class="form-group">'+
1596 '<label class="radio-inline">'+
1597 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-1-'+ wrapperUid +'" value="1"'+ (embedShouldBeResponsive ? ' checked' : '') +'> Yes'+
1598 '</label>'+
1599 '<label class="radio-inline">'+
1600 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-0-'+ wrapperUid +'" value="0"'+ (!embedShouldBeResponsive ? ' checked' : '') +'> No'+
1601 '</label>'+
1602 '</div>'+
1603 '</div>' : '')+
1604 '<div class="col-md-6">'+
1605 '<div class="form-group">'+
1606 '<label for="input-width-'+ wrapperUid +'">Width</label>'+
1607 '<input class="form-control" type="integer" id="input-width-'+ wrapperUid +'" value="'+ embedWidth +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1608 '</div>'+
1609 '</div>'+
1610 '<div class="col-md-6">'+
1611 '<div class="form-group">'+
1612 '<label for="input-height-'+ wrapperUid +'">Height</label>'+
1613 '<input class="form-control" type="integer" id="input-height-'+ wrapperUid +'" value="'+ embedHeight +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1614 '</div>'+
1615 '</div>'+
1616 '</div>'+
1617 '</form>',
1618 buttons: {
1619 danger: {
1620 label: "Cancel",
1621 className: "btn-default",
1622 callback: function() {
1623 // do nothing
1624 self.activeWrapperForModal = null;
1625 }
1626 },
1627 success: {
1628 label: "Save",
1629 className: "btn-primary",
1630 callback: function() {
1631 var $wrapper = self.activeWrapperForModal;
1632
1633 // Select the current wrapper as a base for the new element
1634 editorInstance.focus();
1635 editorInstance.selection.select($wrapper[0]);
1636
1637 $wrapper.children().remove();
1638 $wrapper.remove();
1639
1640 if (response.canBeResponsive) {
1641 if ($('#form-'+ wrapperUid +' input[name="input-responsive-'+ wrapperUid +'"]:checked').val().isFalse()) {
1642 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1643 if (parseInt(embedCustomWidth) > 0) {
1644 customAttributes['width'] = embedCustomWidth;
1645 }
1646
1647 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1648 if (parseInt(embedCustomHeight) > 0) {
1649 customAttributes['height'] = embedCustomHeight;
1650 }
1651
1652 customAttributes['responsive'] = "false";
1653 } else {
1654 delete customAttributes['width'];
1655 delete customAttributes['height'];
1656
1657 customAttributes['responsive'] = "true";
1658 }
1659 } else {
1660 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1661 if (parseInt(embedCustomWidth) > 0) {
1662 customAttributes['width'] = embedCustomWidth;
1663 }
1664
1665 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1666 if (parseInt(embedCustomHeight) > 0) {
1667 customAttributes['height'] = embedCustomHeight;
1668 }
1669 }
1670
1671 var customAttributesList = [];
1672 if (!!Object.keys(customAttributes).length) {
1673 for (var attrName in customAttributes) {
1674 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1675 }
1676 }
1677
1678 var shortcode = '['+ $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? " "+ customAttributesList.join(" ") : "") +']'+ $('#input-url-'+ wrapperUid).val() +'[/'+ $data.EMBEDPRESS_SHORTCODE +']';
1679 // We do not directly replace the node because it was causing a bug on a second edit attempt
1680 editorInstance.execCommand('mceInsertContent', false, shortcode);
1681
1682 self.configureWrappers(editorInstance);
1683 }
1684 }
1685 }
1686 });
1687
1688 $('form[embedpress]').on('change', 'input[type="radio"]', function(e) {
1689 var self = $(this);
1690 var form = self.parents('form[embedpress]');
1691
1692 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
1693 });
1694 },
1695 complete: function(request, textStatus) {
1696 $('.loader-indicator', $wrapper).removeClass('is-loading');
1697
1698 setTimeout(function() {
1699 $('.loader-indicator', $wrapper).remove();
1700 }, 350);
1701 },
1702 dataType: "json",
1703 async: true
1704 });
1705 }, 200);
1706
1707 return false;
1708 };
1709
1710 /**
1711 * Method executed when the remove button is clicked. It will remove
1712 * the preview and embed code, adding a mark to ignore the url
1713 *
1714 * @param Object e The event
1715 * @return void
1716 */
1717 self.onClickRemoveButton = function(e, editorInstance) {
1718 // Prevent edition of the panel
1719 self.cancelEvent(e, editorInstance);
1720
1721 var $wrapper = self.activeWrapper;
1722
1723 $wrapper.children().remove();
1724 $wrapper.remove();
1725
1726 return false;
1727 };
1728
1729 self.recursivelyAddClass = function(element, className) {
1730 $(element).children().each(function(index, child) {
1731 $(child).addClass(className);
1732
1733 var grandChild = $(child).children();
1734 if (grandChild.length > 0) {
1735 self.recursivelyAddClass(child, className)
1736 }
1737 });
1738 };
1739
1740 self.setInterval = function(callback, time, timeout) {
1741 var elapsed = 0;
1742 var iteraction = 0;
1743
1744 var interval = window.setInterval(function() {
1745 elapsed += time;
1746 iteraction++;
1747
1748 if (elapsed <= timeout) {
1749 callback(iteraction, elapsed);
1750 } else {
1751 self.stopInterval(interval);
1752 }
1753 }, time);
1754
1755 return interval;
1756 };
1757
1758 self.stopInterval = function(interval) {
1759 window.clearInterval(interval);
1760 interval = null;
1761 };
1762
1763 /**
1764 * Configure unconfigured embed wrappers, adding events and css
1765 * @return void
1766 */
1767 self.configureWrappers = function(editorInstance) {
1768 window.setTimeout(
1769 function configureWrappersTimeOut() {
1770 var doc = editorInstance.getDoc(),
1771 total = 0,
1772 $wrapper = null,
1773 $iframe = null;
1774
1775 // Get all the wrappers
1776 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1777 total = wrappers.length;
1778 if (total > 0) {
1779 for (var i = 0; i < total; i++) {
1780 $wrapper = $(wrappers[i]);
1781
1782 // Check if the wrapper wasn't already configured
1783 if ($wrapper.data('configured') != true) {
1784 // A timeout was set to avoid block the content loading
1785 window.setTimeout(function() {
1786 // @todo: Check if we need a limit of levels to avoid use too much resources
1787 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1788 }, 500);
1789
1790 // Fix the wrapper size. Wait until find the child iframe. L
1791 var interval = self.setInterval(function(iteraction) {
1792 var $childIframes = $wrapper.find('iframe');
1793
1794 if ($childIframes.length > 0) {
1795 $.each($childIframes, function(index, iframe) {
1796 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1797 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1798 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1799 ) {
1800 $wrapper.css('width', $(iframe).width() + 'px');
1801 self.stopInterval(interval);
1802 }
1803 });
1804 }
1805 }, 500, 8000);
1806
1807 $wrapper.data('configured', true);
1808 }
1809 }
1810 }
1811 },
1812 200
1813 );
1814 };
1815
1816 /**
1817 * Hide the controller panel
1818 *
1819 * @return void
1820 */
1821 self.hidePreviewControllerPanel = function() {
1822 if (self.controllerPanelIsActive()) {
1823 $(self.activeControllerPanel).addClass('hidden');
1824 self.activeControllerPanel = null;
1825 self.activeWrapper = null;
1826 }
1827 };
1828
1829 /**
1830 * Get an element by id in the editor's content
1831 *
1832 * @param String id The element id
1833 * @return Element The found element or null, wrapped by jQuery
1834 */
1835 self.getElementInContentById = function(id, editorInstance) {
1836 var doc = editorInstance.getDoc();
1837
1838 return $(doc.getElementById(id));
1839 };
1840
1841 /**
1842 * Show the controller panel
1843 *
1844 * @param element $wrapper The wrapper which will be activate
1845 * @return void
1846 */
1847 self.displayPreviewControllerPanel = function($wrapper, editorInstance) {
1848 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1849 self.hidePreviewControllerPanel();
1850 }
1851
1852 if (!self.controllerPanelIsActive() && !$wrapper.hasClass('is-loading')) {
1853 var uid = $wrapper.data('uid');
1854 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid, editorInstance);
1855
1856 if (!$panel.data('event-set')) {
1857 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid, editorInstance);
1858 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid, editorInstance);
1859
1860 self.addEvent('mousedown', $editButton, function(e) {
1861 self.onClickEditButton(e, editorInstance);
1862 });
1863
1864 self.addEvent('mousedown', $removeButton, function(e) {
1865 self.onClickRemoveButton(e, editorInstance);
1866 });
1867
1868 $panel.data('event-set', true);
1869 }
1870
1871 // Update the position of the control bar
1872 var next = $panel.next()[0];
1873 if (typeof next !== 'undefined') {
1874 if (next.nodeName.toLowerCase() === 'iframe') {
1875 $panel.css('left', ($(next).width() / 2));
1876 }
1877 }
1878
1879 // Show the bar
1880 $panel.removeClass('hidden');
1881
1882 self.activeControllerPanel = $panel;
1883 self.activeWrapper = $wrapper;
1884 }
1885 };
1886 };
1887
1888 if (!window.EmbedPress) {
1889 window.EmbedPress = new EmbedPress();
1890 }
1891
1892 window.EmbedPress.init($data.previewSettings);
1893 });
1894 })(jQuery, String, $data);
1895