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