PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 1.4.2
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v1.4.2
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
1851 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($, String, $data, undefined) {
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 EmbedPress = 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 $(self).triggerHandler('EmbedPress.beforeEmbed', {
765 'url' : rawUrl,
766 'meta': {
767 'attributes': customAttributes || {}
768 }
769 });
770
771 // Get the parsed embed code from the EmbedPress plugin
772 self.getParsedContent(url, function getParsedContentCallback(result) {
773 var embeddedContent = (typeof result.data === "object" ? result.data.embed : result.data).stripShortcode($data.EMBEDPRESS_SHORTCODE);
774
775 var $wrapper = $(self.getElementInContentById('embedpress_wrapper_' + uid));
776 var wrapperParent = $($wrapper.parent());
777
778 // Check if $wrapper was rendered inside a <p> element.
779 if (wrapperParent.prop('tagName') && wrapperParent.prop('tagName').toUpperCase() === "P") {
780 wrapperParent.replaceWith($wrapper);
781 // Check if there's at least one "space" after $wrapper.
782 var nextSibling = $($wrapper).next();
783 if (!nextSibling.length || nextSibling.prop('tagName').toUpperCase() !== "P") {
784 //$('<p>&nbsp;</p>').insertAfter($wrapper);
785 }
786 nextSibling = null;
787 }
788 wrapperParent = null;
789
790 // Check if the url could not be embedded for some reason.
791 if (rawUrl === embeddedContent) {
792 // Echoes the raw url
793 $wrapper.replaceWith($('<p>'+ rawUrl +'</p>'));
794 return;
795 }
796
797 $wrapper.removeClass('is-loading');
798
799 // Parse as DOM element
800 var $content;
801 try {
802 $content = $(embeddedContent);
803 } catch(err) {
804 // Fallback to a div, if the result is not a html markup, e.g. a url
805 $content = $('<div>');
806 $content.html(embeddedContent);
807 }
808
809 if (!$('iframe', $content).length) {
810 var contentWrapper = $($content).clone();
811 contentWrapper.html('');
812
813 var dom = self.editor.dom;
814
815 $wrapper.removeClass('embedpress_placeholder');
816
817 $wrapper.append(contentWrapper);
818
819 setTimeout(function() {
820 self.editor.undoManager.transact(function() {
821 var iframe = self.editor.getDoc().createElement('iframe');
822 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
823 iframe.frameBorder = '0';
824 iframe.allowTransparency = 'true';
825 iframe.scrolling = 'no';
826 iframe.class = "wpview-sandbox";
827 iframe.style.width = (customAttributes.width ? customAttributes.width +'px' : '100%');
828
829 dom.add(contentWrapper, iframe);
830 var iframeWindow = iframe.contentWindow;
831 // Content failed to load.
832 if (!iframeWindow) {
833 return;
834 }
835
836 var iframeDoc = iframeWindow.document;
837
838 $(iframe).load(function() {
839 var maximumChecksAllowed = 100;
840 var checkIndex = 0;
841 var checkerInterval = setInterval(function() {
842 if (checkIndex === maximumChecksAllowed) {
843 clearInterval(checkerInterval);
844
845 setTimeout(function() {
846 iframe.height = $(iframeDoc.body).height();
847 iframe.width = $(iframeDoc.body).width();
848
849 $wrapper.attr('width', iframe.width);
850 $wrapper.css('width', iframe.width + 'px');
851 }, 250);
852 } else {
853 if (customAttributes.height) {
854 iframe.height = customAttributes.height;
855 iframe.style.height = customAttributes.height +'px';
856 } else {
857 iframe.height = iframeDoc.body.scrollHeight;
858 }
859
860 if (customAttributes.width) {
861 iframe.width = customAttributes.width;
862 iframe.style.width = customAttributes.width +'px';
863 } else {
864 iframe.width = $(iframeDoc.body).width();
865 }
866
867 checkIndex++;
868 }
869 }, 100);
870 });
871
872 iframeDoc.open();
873 iframeDoc.write(
874 '<!DOCTYPE html>'+
875 '<html>'+
876 '<head>'+
877 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'+
878 '<style>'+
879 'html {'+
880 'background: transparent;'+
881 'padding: 0;'+
882 'margin: 0;'+
883 '}'+
884 'body#wpview-iframe-sandbox {'+
885 'background: transparent;'+
886 'padding: 1px 0 !important;'+
887 'margin: -1px 0 0 !important;'+
888 '}'+
889 'body#wpview-iframe-sandbox:before,'+
890 'body#wpview-iframe-sandbox:after {'+
891 'display: none;'+
892 'content: "";'+
893 '}'+
894 '</style>'+
895 '</head>'+
896 '<body id="wpview-iframe-sandbox" class="'+ self.editor.getBody().className +'" style="width: 100%; display: inline-block;">'+
897 $content.html() +
898 '</body>'+
899 '</html>'
900 );
901 iframeDoc.close();
902 });
903 }, 50);
904 } else {
905 $wrapper.removeClass('embedpress_placeholder');
906
907 self.appendElementsIntoWrapper($content, $wrapper);
908 }
909
910 //$wrapper.append($('<span class="mce-shim"></span>'));
911 $wrapper.append($('<span class="wpview-end"></span>'));
912
913 if (result && result.data && typeof result.data === "object") {
914 result.data.width = $($wrapper).width();
915 result.data.height = $($wrapper).height();
916 }
917
918 $(self).triggerHandler('EmbedPress.afterEmbed', {
919 'meta' : result.data,
920 'url' : rawUrl,
921 'wrapper': $wrapper
922 });
923 });
924 };
925
926 self.appendElementsIntoWrapper = function(elementsList, wrapper) {
927 if (elementsList.length > 0) {
928 $.each(elementsList, function appendElementIntoWrapper(elementIndex, element) {
929 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
930 if (element.tagName.toLowerCase() !== 'script') {
931 wrapper.append($(element));
932
933 if (element.tagName.toLowerCase() === 'iframe') {
934 $(element).ready(function() {
935 window.setTimeout(function() {
936 $.each(self.editor.dom.select('div.embedpress_wrapper iframe'), function(elementIndex, iframe) {
937 self.fixIframeSize(iframe);
938 });
939 }, 300);
940 });
941 } else if (element.tagName.toLowerCase() === "div") {
942 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
943 // This ensures that the embed wrapper have the same width as its content
944 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
945 }
946
947 $(element).css('max-width', $($(element).parents('body').get(0)).width());
948 }
949 }
950
951 self.loadAsyncDynamicJsCodeFromElement(element, wrapper);
952 });
953 }
954
955 return wrapper;
956 };
957
958 self.encodeEmbedURLSpecialChars = function(content) {
959 if (content.match(SHORTCODE_REGEXP)) {
960 var subject = content.replace(SHORTCODE_REGEXP, '');
961
962 if (!subject.isValidUrl()) {
963 return content;
964 }
965
966 content = subject;
967 subject = null;
968 }
969
970 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
971 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
972
973 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
974 content = content.replace('@', '::__at__::').trim();
975
976 return content;
977 };
978
979 self.decodeEmbedURLSpecialChars = function(content, applyShortcode, attributes) {
980 var encodingRegexpRule = /embedpress(s?):\/\//;
981 applyShortcode = (typeof applyShortcode === "undefined") ? true : applyShortcode;
982 attributes = (typeof attributes === "undefined") ? {} : attributes;
983
984 var isEncoded = content.match(encodingRegexpRule);
985
986 // Restore http[s] in the url (converted to bypass autolink plugin)
987 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
988 content = content.replace('::__at__::', '@').trim();
989
990 if ("class" in attributes) {
991 var classesList = attributes.class.split(/\s/g);
992 var shouldRemoveDynamicWidthClass = false;
993 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
994 if (classesList[classIndex] === "dynamic-width") {
995 shouldRemoveDynamicWidthClass = classIndex;
996 break;
997 }
998 }
999
1000 if (shouldRemoveDynamicWidthClass !== false) {
1001 classesList.splice(shouldRemoveDynamicWidthClass, 1);
1002
1003 if (classesList.length === 0) {
1004 delete attributes.class;
1005 }
1006
1007 attributes.class = classesList.join(" ");
1008 }
1009
1010 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
1011 }
1012
1013 if (isEncoded && applyShortcode) {
1014 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE;
1015 if (!!Object.keys(attributes).length) {
1016 var attrValue;
1017
1018 for (var attrName in attributes) {
1019 attrValue = attributes[attrName];
1020
1021 // Prevent `class` property being empty an treated as a boolean param
1022 if (attrName.toLowerCase() === "class" && !attrValue.length) {
1023 continue;
1024 }
1025 else {
1026 if (attrValue.isBoolean()) {
1027 shortcode += " ";
1028 if (attrValue.isFalse()) {
1029 shortcode += "!";
1030 }
1031
1032 shortcode += attrName
1033 } else {
1034 shortcode += ' '+ attrName +'="'+ attrValue +'"';
1035 }
1036 }
1037 }
1038 attrValue = attrName = null;
1039 }
1040
1041 content = shortcode + ']' + content + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
1042 }
1043
1044 return content;
1045 };
1046
1047 /**
1048 * Method executed after find the editor. It will make additional
1049 * configurations and add the content's stylesheets for the preview
1050 *
1051 * @return void
1052 */
1053 self.onFindEditor = function() {
1054 self.each = tinymce.each;
1055 self.extend = tinymce.extend;
1056 self.JSON = tinymce.util.JSON;
1057 self.Node = tinymce.html.Node;
1058
1059 function onFindEditorCallback() {
1060 $(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) + '">'));
1061
1062 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?v=' + self.params.versionUID);
1063 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?v=' + self.params.versionUID);
1064 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?v=' + self.params.versionUID);
1065 self.addEvent('paste', self.editor, self.onPaste);
1066 self.addEvent('nodechange', self.editor, self.onNodeChange);
1067 self.addEvent('keydown', self.editor, self.onKeyDown);
1068
1069 self.addEvent('undo', self.editor, self.onUndo); // TinyMCE
1070 self.addEvent('undo', self.editor.undoManager, self.onUndo); // JCE
1071
1072 var doc = self.editor.getDoc();
1073 $(doc).on('mouseenter', '.embedpress_wrapper', self.onMouseEnter);
1074 $(doc).on('mouseout', '.embedpress_wrapper', self.onMouseOut);
1075 $(doc).on('mousedown', '.embedpress_wrapper > .embedpress_controller_panel', self.cancelEvent);
1076 doc = null;
1077
1078 // Add the node filter that will convert the url into the preview box for the embed code
1079 // @todo: Recognize <a> tags as well
1080 self.editor.parser.addNodeFilter('#text', function addNodeFilterIntoParser(nodes, arg) {
1081 self.each(nodes, function eachNodeInParser(node) {
1082 var subject = node.value.trim();
1083 if (!subject.isValidUrl()) {
1084 if (!subject.match(SHORTCODE_REGEXP)) {
1085 return;
1086 }
1087 }
1088
1089 subject = self.decodeEmbedURLSpecialChars(subject);
1090 if (!subject.isValidUrl()) {
1091 if (!subject.match(SHORTCODE_REGEXP)) {
1092 return;
1093 }
1094 }
1095
1096 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
1097
1098 // These patterns need to have groups for the pre and post texts
1099 var patterns = self.getProvidersURLPatterns();
1100
1101 (function tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex) {
1102 if (urlPatternIndex < patterns.length) {
1103 var urlPattern = patterns[urlPatternIndex];
1104 var urlPatternRegex = new RegExp(urlPattern);
1105
1106 var url = self.decodeEmbedURLSpecialChars(subject).trim();
1107
1108 var matches = url.match(urlPatternRegex);
1109 // Check if content matches the url pattern.
1110 if (matches && matches !== null && !!matches.length) {
1111 url = self.encodeEmbedURLSpecialChars(matches[2]);
1112
1113 var wrapper = self.addURLsPlaceholder(node, url);
1114
1115 setTimeout(function() {
1116 var doc = self.editor.getDoc();
1117
1118 var previewWrapper = $(doc.querySelector('#'+ wrapper.attributes.map['id']));
1119 var previewWrapperParent = $(previewWrapper.parent());
1120
1121 if (previewWrapperParent && previewWrapperParent.prop('tagName') && previewWrapperParent.prop('tagName').toUpperCase() === "P") {
1122 previewWrapperParent.replaceWith(previewWrapper);
1123 }
1124
1125 var previewWrapperOlderSibling = previewWrapper.prev();
1126 if (previewWrapperOlderSibling && previewWrapperOlderSibling.prop('tagName') && previewWrapperOlderSibling.prop('tagName').toUpperCase() === "P" && !previewWrapperOlderSibling.html().replace(/\&nbsp\;/i, '').length) {
1127 previewWrapperOlderSibling.remove();
1128 }
1129
1130 var previewWrapperYoungerSibling = previewWrapper.next();
1131 if (previewWrapperYoungerSibling && previewWrapperYoungerSibling.length && previewWrapperYoungerSibling.prop('tagName').toUpperCase() === "P") {
1132 if (!previewWrapperYoungerSibling.next().length && !previewWrapperYoungerSibling.html().replace(/\&nbsp\;/i, '').length) {
1133 previewWrapperYoungerSibling.remove();
1134 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
1135 } else {
1136 previewWrapperYoungerSibling.remove();
1137 }
1138 } else {
1139 $('<p>&nbsp;</p>').insertAfter(previewWrapper);
1140 }
1141
1142 setTimeout(function() {
1143 self.editor.selection.select(self.editor.getBody(), true);
1144 self.editor.selection.collapse(false);
1145 }, 50);
1146 }, 50);
1147 } else {
1148 // No match. So we move on to check the next url pattern.
1149 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
1150 }
1151 }
1152 })(0);
1153 });
1154 });
1155
1156 // Add the filter that will convert the preview box/embed code back to the raw url
1157 self.editor.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer(nodes, arg) {
1158 self.each(nodes, function eachNodeInSerializer(node) {
1159 var nodeClasses = (node.attributes.map.class || "").split(' ');
1160 var wrapperFactoryClasses = ["embedpress_wrapper", "embedpress_placeholder", "wpview", "wpview-wrap"];
1161
1162 var isWrapped = nodeClasses.filter(function(n) {
1163 return wrapperFactoryClasses.indexOf(n) != -1;
1164 }).length > 0;
1165
1166 if (isWrapped) {
1167 var factoryAttributes = ["id", "style", "data-loading-text", "data-uid", "data-url"];
1168 var customAttributes = {};
1169 var dataPrefix = "data-";
1170 for (var attr in node.attributes.map) {
1171 if (attr.toLowerCase() !== "class") {
1172 if (factoryAttributes.indexOf(attr) < 0) {
1173 // Remove the "data-" prefix for more readability
1174 customAttributes[attr.replace(dataPrefix, "")] = node.attributes.map[attr];
1175 }
1176 } else {
1177 var customClasses = [];
1178 for (var wrapperClassIndex in nodeClasses) {
1179 var wrapperClass = nodeClasses[wrapperClassIndex];
1180 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
1181 customClasses.push(wrapperClass);
1182 }
1183 }
1184
1185 if (!!customClasses.length) {
1186 customAttributes.class = customClasses.join(" ");
1187 }
1188 }
1189 }
1190
1191 var p = new self.Node('p', 1);
1192
1193 var text = new self.Node('#text', 3);
1194 text.value = self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes);
1195
1196 p.append(text.clone());
1197
1198 node.replace(text);
1199 text.replace(p);
1200 }
1201 });
1202 });
1203
1204 self.editor.serializer.addNodeFilter('p', function addNodeFilterIntoSerializer(nodes, arg) {
1205 self.each(nodes, function eachNodeInSerializer(node) {
1206 if (node.firstChild == node.lastChild) {
1207 if (node.firstChild && "value" in node.firstChild && (node.firstChild.value === "&nbsp;" || !node.firstChild.value.trim().length)) {
1208 node.remove();
1209 }
1210 }
1211 });
1212 });
1213
1214 // Add event to reconfigure wrappers every time the content is loaded
1215 tinymce.each(tinymce.editors, function onEachEditor(editor) {
1216 self.addEvent('loadContent', editor, function onInitEditor(ed) {
1217 self.configureWrappers();
1218 });
1219 });
1220
1221 // Add the edit form
1222
1223 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
1224 window.setTimeout(
1225 function afterTimeoutOnFindEditor() {
1226 /*
1227 * This is required because after load/refresh the page, the
1228 * onLoadContent is not being triggered automatically, so
1229 * we force the event
1230 */
1231 self.editor.load();
1232 },
1233 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1234 500
1235 );
1236 }
1237
1238 // Let's make sure the inner doc has been fully loaded first.
1239 var checkTimesLimit = 100;
1240 var checkIndex = 0;
1241 var statusCheckerInterval = setInterval(function() {
1242 if (checkIndex === checkTimesLimit) {
1243 clearInterval(statusCheckerInterval);
1244 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1245 } else {
1246 var doc = self.editor.getDoc();
1247 if (doc) {
1248 clearInterval(statusCheckerInterval);
1249 onFindEditorCallback();
1250 } else {
1251 checkIndex++;
1252 }
1253 }
1254 }, 250);
1255 };
1256
1257 self.fixIframeSize = function(iframe) {
1258 var maxWidth = 480;
1259 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1260 var ratio = $(iframe).height() / $(iframe).width();
1261 $(iframe).width(maxWidth);
1262 $(iframe).height(maxWidth * ratio);
1263 $(iframe).css('max-width', maxWidth);
1264 $(iframe).attr('max-width', maxWidth);
1265
1266 $(iframe).data('size-fixed', true);
1267 }
1268 }
1269
1270 /**
1271 * Function triggered on mouse enter the wrapper
1272 *
1273 * @param object e The event
1274 * @return void
1275 */
1276 self.onMouseEnter = function(e) {
1277 self.displayPreviewControllerPanel($(e.currentTarget));
1278 };
1279
1280 /**
1281 * Function triggered on mouse get out of the wrapper
1282 *
1283 * @param object e The event
1284 * @return void
1285 */
1286 self.onMouseOut = function(e) {
1287 // Check if the destiny is not a child element
1288 // Chrome
1289 if (self.isDefined(e.toElement)) {
1290 if (e.toElement.parentElement == e.fromElement
1291 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1292 ) {
1293 return false;
1294 }
1295 }
1296
1297 // Firefox
1298 if (self.isDefined(e.relatedTarget)) {
1299 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1300 return false;
1301 }
1302 }
1303
1304 self.hidePreviewControllerPanel();
1305 };
1306
1307 /**
1308 * Function called on paste event in the editor
1309 *
1310 * @param object e The event
1311 * @return void
1312 */
1313 self.onPaste = function(e, b) {
1314 var event = null;
1315
1316 // Prevent default paste behavior. We have 2 arguments because the difference between JCE and TinyMCE.
1317 // Sometimes, the argument e is the editor, instead of the event.
1318 if (e.preventDefault) {
1319 event = e;
1320 } else {
1321 event = b;
1322 }
1323
1324 // Check for clipboard data in various places for cross-browser compatibility and get its data as text.
1325 var content = ((event.originalEvent || event).clipboardData || window.clipboardData).getData('Text');
1326
1327 // Check if the pasted content has a recognized embed url pattern
1328 var patterns = self.getProvidersURLPatterns();
1329
1330 (function tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex) {
1331 if (urlPatternIndex < content.length) {
1332 var urlPattern = patterns[urlPatternIndex];
1333
1334 var urlPatternRegex = new RegExp(urlPattern);
1335 var matches = content.match(urlPatternRegex) || null;
1336
1337 // Check if content matches the url pattern.
1338 if (matches && matches !== null && !!matches.length) {
1339 // Cancel the default behavior.
1340 event.preventDefault();
1341 event.stopPropagation();
1342
1343 // Remove "www." subdomain from Slideshare.net urls that was causing bugs on TinyMCE.
1344 content = content.replace(/www\.slideshare\.net\//i, 'slideshare.net/');
1345
1346 // Let TinyMCE do the heavy lifting for inserting that content into the self.editor.
1347 // We cancel the default behavior and insert the embed-content using a command to trigger the node change and the parser.
1348 self.editor.execCommand('mceInsertContent', false, content);
1349
1350 self.configureWrappers();
1351 } else {
1352 // No match. So we move on to check the next url pattern.
1353 tryToMatchContentAgainstUrlPatternWithIndex(urlPatternIndex + 1);
1354 }
1355 }
1356 })(0);
1357 };
1358
1359 /**
1360 * Method trigered on every node change, to detect new lines. It will
1361 * try to fix a default behavior for some editors of clone the parent
1362 * element when adding a line break. This will clone the embed wrapper
1363 * if we set the cursor after a preview wrapper and hit enter.
1364 *
1365 * @param object e The event
1366 * @return void
1367 */
1368 self.onNodeChange = function(e) {
1369 // Fix the clone parent on break lines issue
1370 // Check if a line break was added
1371 if (e.element.tagName === 'BR') {
1372 // Check one of the parent elements is a clonned embed wrapper
1373 if (e.parents.length > 0) {
1374 $.each(e.parents, function(index, parent) {
1375 if ($(parent).hasClass('embedpress_wrapper')) {
1376 // Remove the cloned wrapper and replace with a 'br' tag
1377 $(parent).replaceWith($('<br>'));
1378 }
1379 });
1380 }
1381 } else if (e.element.tagName === "IFRAME") {
1382 if (e.parents.length > 0) {
1383 $.each(e.parents, function(index, parent) {
1384 parent = $(parent);
1385 if (parent.hasClass('embedpress_wrapper')) {
1386 var wrapper = $('.embedpress-wrapper', parent);
1387 if (wrapper.length > 1) {
1388 wrapper.get(0).remove();
1389 }
1390 }
1391 });
1392 }
1393 }
1394 };
1395
1396 self.onKeyDown = function(e) {
1397 var node = self.editor.selection.getNode();
1398
1399 if (e.keyCode == 8 || e.keyCode == 46) {
1400 if (node.nodeName.toLowerCase() === 'p') {
1401 var children = $(node).children();
1402 if (children.length > 0) {
1403 $.each(children, function() {
1404 // On delete, make sure to remove the wrapper and children, not only the wrapper
1405 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1406 $(this).remove();
1407
1408 self.editor.focus();
1409 }
1410 });
1411 }
1412 }
1413 } else {
1414 // Ignore the arrows keys
1415 var arrowsKeyCodes = [37, 38, 39, 40];
1416 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1417
1418 // Check if we are inside a preview wrapper
1419 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1420 // Avoid delete the wrapper or block line break if we are inside the wrapper
1421 if (e.keyCode == 13) {
1422 wrapper = $(self.getWrapperFromChild(node));
1423 if (wrapper.length > 0) {
1424 // Creates a temporary element which will be inserted after the wrapper
1425 var tmpId = '__embedpress__tmp_' + self.makeId();
1426 wrapper.after($('<span id="' + tmpId + '"></span>'));
1427 // Get and select the temporary element
1428 var span = self.editor.dom.select('span#' + tmpId)[0];
1429 self.editor.selection.select(span);
1430 // Remove the temporary element
1431 $(span).remove();
1432 }
1433
1434 return true;
1435 } else {
1436 // If we are inside the embed preview, ignore any key to avoid edition
1437 return self.cancelEvent(e);
1438 }
1439 }
1440 }
1441 }
1442
1443 return true;
1444 }
1445
1446 self.getWrapperFromChild = function(element) {
1447 // Is the wrapper
1448 if ($(element).hasClass('embedpress_wrapper')) {
1449 return element;
1450 } else {
1451 var $parent = $(element).parent();
1452
1453 if ($parent.length > 0) {
1454 return self.getWrapperFromChild($parent[0]);
1455 }
1456 }
1457
1458 return false;
1459 };
1460
1461 self.onUndo = function(e) {
1462 // Force re-render everything
1463 self.editor.load();
1464 };
1465
1466 self.cancelEvent = function(e) {
1467 e.preventDefault();
1468 e.stopPropagation();
1469 self.editor.dom.events.cancel();
1470
1471 return false;
1472 };
1473
1474 /**
1475 * Method executed when the edit button is clicked. It will display
1476 * a field with the current url, to update the current embed's source
1477 * url.
1478 *
1479 * @param Object e The event
1480 * @return void
1481 */
1482 self.onClickEditButton = function(e) {
1483 // Prevent edition of the panel
1484 self.cancelEvent(e);
1485
1486 self.activeWrapperForModal = self.activeWrapper;
1487
1488 var $wrapper = self.activeWrapperForModal;
1489 var wrapperUid = $wrapper.prop('id').replace("embedpress_wrapper_", "");
1490
1491 var customAttributes = {};
1492
1493 var embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1494 var embedItem = $('iframe', $wrapper);
1495 if (!embedItem.length) {
1496 embedItem = null;
1497 }
1498
1499 $.each(embedInnerWrapper.attributes, function() {
1500 if (this.specified) {
1501 if (this.name !== "class") {
1502 customAttributes[this.name.replace('data-', "").toLowerCase()] = this.value;
1503 }
1504 }
1505 });
1506
1507 var embedWidth = (((embedItem && embedItem.width()) || embedInnerWrapper.data('width')) || embedInnerWrapper.width()) || "";
1508 var embedHeight = (((embedItem && embedItem.height()) || embedInnerWrapper.data('height')) || embedInnerWrapper.height()) || "";
1509
1510 embedItem = embedInnerWrapper = null;
1511
1512 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1513
1514 setTimeout(function() {
1515 $.ajax({
1516 type: "GET",
1517 url: "admin-ajax.php",
1518 data: {
1519 action: "embedpress_get_embed_url_info",
1520 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1521 },
1522 beforeSend: function(request, requestSettings) {
1523 $('.loader-indicator', $wrapper).addClass('is-loading');
1524 },
1525 success: function(response) {
1526 if (!response) {
1527 bootbox.alert('Unable to get a valid response from the server.');
1528 return;
1529 }
1530
1531 if (response.canBeResponsive) {
1532 var embedShouldBeResponsive = true;
1533 if ("width" in customAttributes || "height" in customAttributes) {
1534 embedShouldBeResponsive = false;
1535 } else if ("responsive" in customAttributes && customAttributes['responsive'].isFalse()) {
1536 embedShouldBeResponsive = false;
1537 }
1538 }
1539
1540 bootbox.dialog({
1541 className: "embedpress-modal",
1542 title: "Editing Embed properties",
1543 message: '<form id="form-'+ wrapperUid +'" embedpress>'+
1544 '<div class="row">'+
1545 '<div class="col-md-12">'+
1546 '<div class="form-group">'+
1547 '<label for="input-url-'+ wrapperUid +'">Url</label>'+
1548 '<input class="form-control" type="url" id="input-url-'+ wrapperUid +'" value="'+ self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) +'">'+
1549 '</div>'+
1550 '</div>'+
1551 '</div>'+
1552 '<div class="row">'+
1553 (response.canBeResponsive ?
1554 '<div class="col-md-12">'+
1555 '<label>Responsive</label>'+
1556 '<div class="form-group">'+
1557 '<label class="radio-inline">'+
1558 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-1-'+ wrapperUid +'" value="1"'+ (embedShouldBeResponsive ? ' checked' : '') +'> Yes'+
1559 '</label>'+
1560 '<label class="radio-inline">'+
1561 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-0-'+ wrapperUid +'" value="0"'+ (!embedShouldBeResponsive ? ' checked' : '') +'> No'+
1562 '</label>'+
1563 '</div>'+
1564 '</div>' : '')+
1565 '<div class="col-md-6">'+
1566 '<div class="form-group">'+
1567 '<label for="input-width-'+ wrapperUid +'">Width</label>'+
1568 '<input class="form-control" type="integer" id="input-width-'+ wrapperUid +'" value="'+ embedWidth +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1569 '</div>'+
1570 '</div>'+
1571 '<div class="col-md-6">'+
1572 '<div class="form-group">'+
1573 '<label for="input-height-'+ wrapperUid +'">Height</label>'+
1574 '<input class="form-control" type="integer" id="input-height-'+ wrapperUid +'" value="'+ embedHeight +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1575 '</div>'+
1576 '</div>'+
1577 '</div>'+
1578 '</form>',
1579 buttons: {
1580 danger: {
1581 label: "Cancel",
1582 className: "btn-default",
1583 callback: function() {
1584 // do nothing
1585 self.activeWrapperForModal = null;
1586 }
1587 },
1588 success: {
1589 label: "Save",
1590 className: "btn-primary",
1591 callback: function() {
1592 var $wrapper = self.activeWrapperForModal;
1593
1594 // Select the current wrapper as a base for the new element
1595 self.editor.focus();
1596 self.editor.selection.select($wrapper[0]);
1597
1598 $wrapper.children().remove();
1599 $wrapper.remove();
1600
1601 if (response.canBeResponsive) {
1602 if ($('#form-'+ wrapperUid +' input[name="input-responsive-'+ wrapperUid +'"]:checked').val().isFalse()) {
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 customAttributes['responsive'] = "false";
1614 } else {
1615 delete customAttributes['width'];
1616 delete customAttributes['height'];
1617
1618 customAttributes['responsive'] = "true";
1619 }
1620 } else {
1621 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1622 if (parseInt(embedCustomWidth) > 0) {
1623 customAttributes['width'] = embedCustomWidth;
1624 }
1625
1626 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1627 if (parseInt(embedCustomHeight) > 0) {
1628 customAttributes['height'] = embedCustomHeight;
1629 }
1630 }
1631
1632 var customAttributesList = [];
1633 if (!!Object.keys(customAttributes).length) {
1634 for (var attrName in customAttributes) {
1635 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1636 }
1637 }
1638
1639 var shortcode = '['+ $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? " "+ customAttributesList.join(" ") : "") +']'+ $('#input-url-'+ wrapperUid).val() +'[/'+ $data.EMBEDPRESS_SHORTCODE +']';
1640 // We do not directly replace the node because it was causing a bug on a second edit attempt
1641 self.editor.execCommand('mceInsertContent', false, shortcode);
1642
1643 self.configureWrappers();
1644 }
1645 }
1646 }
1647 });
1648
1649 $('form[embedpress]').on('change', 'input[type="radio"]', function(e) {
1650 var self = $(this);
1651 var form = self.parents('form[embedpress]');
1652
1653 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
1654 });
1655 },
1656 complete: function(request, textStatus) {
1657 $('.loader-indicator', $wrapper).removeClass('is-loading');
1658
1659 setTimeout(function() {
1660 $('.loader-indicator', $wrapper).remove();
1661 }, 350);
1662 },
1663 dataType: "json",
1664 async: true
1665 });
1666 }, 200);
1667
1668 return false;
1669 };
1670
1671 /**
1672 * Method executed when the remove button is clicked. It will remove
1673 * the preview and embed code, adding a mark to ignore the url
1674 *
1675 * @param Object e The event
1676 * @return void
1677 */
1678 self.onClickRemoveButton = function(e) {
1679 // Prevent edition of the panel
1680 self.cancelEvent(e);
1681
1682 var $wrapper = self.activeWrapper;
1683
1684 $wrapper.children().remove();
1685 $wrapper.remove();
1686
1687 return false;
1688 };
1689
1690 self.recursivelyAddClass = function(element, className) {
1691 $(element).children().each(function(index, child) {
1692 $(child).addClass(className);
1693
1694 var grandChild = $(child).children();
1695 if (grandChild.length > 0) {
1696 self.recursivelyAddClass(child, className)
1697 }
1698 });
1699 };
1700
1701 self.setInterval = function(callback, time, timeout) {
1702 var elapsed = 0;
1703 var iteraction = 0;
1704
1705 var interval = window.setInterval(function() {
1706 elapsed += time;
1707 iteraction++;
1708
1709 if (elapsed <= timeout) {
1710 callback(iteraction, elapsed);
1711 } else {
1712 self.stopInterval(interval);
1713 }
1714 }, time);
1715
1716 return interval;
1717 };
1718
1719 self.stopInterval = function(interval) {
1720 window.clearInterval(interval);
1721 interval = null;
1722 };
1723
1724 /**
1725 * Configure unconfigured embed wrappers, adding events and css
1726 * @return void
1727 */
1728 self.configureWrappers = function() {
1729 window.setTimeout(
1730 function configureWrappersTimeOut() {
1731 var doc = self.editor.getDoc(),
1732 total = 0,
1733 $wrapper = null,
1734 $iframe = null;
1735
1736 // Get all the wrappers
1737 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1738 total = wrappers.length;
1739 if (total > 0) {
1740 for (var i = 0; i < total; i++) {
1741 $wrapper = $(wrappers[i]);
1742
1743 // Check if the wrapper wasn't already configured
1744 if ($wrapper.data('configured') != true) {
1745 // A timeout was set to avoid block the content loading
1746 window.setTimeout(function() {
1747 // @todo: Check if we need a limit of levels to avoid use too much resources
1748 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1749 }, 500);
1750
1751 // Fix the wrapper size. Wait until find the child iframe. L
1752 var interval = self.setInterval(function(iteraction) {
1753 var $childIframes = $wrapper.find('iframe');
1754
1755 if ($childIframes.length > 0) {
1756 $.each($childIframes, function(index, iframe) {
1757 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1758 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1759 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1760 ) {
1761 $wrapper.css('width', $(iframe).width() + 'px');
1762 self.stopInterval(interval);
1763 }
1764 });
1765 }
1766 }, 500, 8000);
1767
1768 $wrapper.data('configured', true);
1769 }
1770 }
1771 }
1772 },
1773 200
1774 );
1775 };
1776
1777 /**
1778 * Hide the controller panel
1779 *
1780 * @return void
1781 */
1782 self.hidePreviewControllerPanel = function() {
1783 if (self.controllerPanelIsActive()) {
1784 $(self.activeControllerPanel).addClass('hidden');
1785 self.activeControllerPanel = null;
1786 self.activeWrapper = null;
1787 }
1788 };
1789
1790 /**
1791 * Get an element by id in the editor's content
1792 *
1793 * @param String id The element id
1794 * @return Element The found element or null, wrapped by jQuery
1795 */
1796 self.getElementInContentById = function(id) {
1797 var doc = self.editor.getDoc();
1798
1799 return $(doc.getElementById(id));
1800 };
1801
1802 /**
1803 * Show the controller panel
1804 *
1805 * @param element $wrapper The wrapper which will be activate
1806 * @return void
1807 */
1808 self.displayPreviewControllerPanel = function($wrapper) {
1809 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1810 self.hidePreviewControllerPanel();
1811 }
1812
1813 if (!self.controllerPanelIsActive() && !$wrapper.hasClass('is-loading')) {
1814 var uid = $wrapper.data('uid');
1815 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid);
1816
1817 if (!$panel.data('event-set')) {
1818 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid);
1819 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid);
1820
1821 self.addEvent('mousedown', $editButton, self.onClickEditButton);
1822 self.addEvent('mousedown', $removeButton, self.onClickRemoveButton);
1823
1824 $panel.data('event-set', true);
1825 }
1826
1827 // Update the position of the control bar
1828 var next = $panel.next()[0];
1829 if (typeof next !== 'undefined') {
1830 if (next.nodeName.toLowerCase() === 'iframe') {
1831 $panel.css('left', ($(next).width() / 2));
1832 }
1833 }
1834
1835 // Show the bar
1836 $panel.removeClass('hidden');
1837
1838 self.activeControllerPanel = $panel;
1839 self.activeWrapper = $wrapper;
1840 }
1841 };
1842 };
1843
1844 if (!window.EmbedPress) {
1845 window.EmbedPress = new EmbedPress();
1846 }
1847
1848 window.EmbedPress.init($data.previewSettings);
1849 });
1850 })(jQuery, String, $data);
1851