PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 1.1.2
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v1.1.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
1732 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
362 // VideoPress
363 'videopress.com/v/*',
364
365 // Tumblr
366 '*.tumblr.com/post/*',
367
368 // SmugMug
369 'smugmug.com/*',
370
371 // SlideShare
372 'slideshare.net/*',
373
374 // Reddit
375 'reddit.com/r/[^/]+/comments/*',
376
377 // Photobucket
378 'i*.photobucket.com/albums/*',
379 'gi*.photobucket.com/groups/*',
380
381 // Cloudup
382 'cloudup.com/*',
383
384 // Imgur
385 'imgur.com/*',
386 'i.imgur.com/*',
387
388 // YouTube (http://www.youtube.com/)
389 'youtube.com/watch\\?*',
390
391 // IFTTT (http://www.ifttt.com/)
392 'ifttt.com/recipes/*',
393
394 // Flickr (http://www.flickr.com/)
395 'flickr.com/photos/*/*',
396 'flic.kr/p/*',
397
398 // Viddler (http://www.viddler.com/)
399 'viddler.com/v/*',
400
401 // Hulu (http://www.hulu.com/)
402 'hulu.com/watch/*',
403
404 // Vimeo (http://vimeo.com/)
405 'vimeo.com/*',
406 'vimeo.com/groups/*/videos/*',
407
408 // CollegeHumor (http://www.collegehumor.com/)
409 'collegehumor.com/video/*',
410
411 // Deviantart.com (http://www.deviantart.com)
412 '*.deviantart.com/art/*',
413 '*.deviantart.com/*#/d*',
414 'fav.me/*',
415 'sta.sh/*',
416
417 // SlideShare (http://www.slideshare.net/)
418 'slideshare.net/*/*',
419 'fr.slideshare.net/*/*',
420 'de.slideshare.net/*/*',
421 'es.slideshare.net/*/*',
422 'pt.slideshare.net/*/*',
423
424 // chirbit.com (http://www.chirbit.com/)
425 'chirb.it/*',
426
427 // nfb.ca (http://www.nfb.ca/)
428 '*.nfb.ca/film/*',
429
430 // Scribd (http://www.scribd.com/)
431 'scribd.com/doc/*',
432
433 // Dotsub (http://dotsub.com/)
434 'dotsub.com/view/*',
435
436 // Animoto (http://animoto.com/)
437 'animoto.com/play/*',
438
439 // Rdio (http://rdio.com/)
440 '*.rdio.com/artist/*',
441 '*.rdio.com/people/*',
442
443 // MixCloud (http://mixcloud.com/)
444 'mixcloud.com/*/*/',
445
446 // FunnyOrDie (http://www.funnyordie.com/)
447 'funnyordie.com/videos/*',
448
449 // Ted (http://ted.com)
450 'ted.com/talks/*',
451
452 // Sapo Videos (http://videos.sapo.pt)
453 'videos.sapo.pt/*',
454
455 // Official FM (http://official.fm)
456 'official.fm/tracks/*',
457 'official.fm/playlists/*',
458
459 // HuffDuffer (http://huffduffer.com)
460 'huffduffer.com/*/*',
461
462 // Shoudio (http://shoudio.com)
463 'shoudio.com/*',
464 'shoud.io/*',
465
466 // Moby Picture (http://www.mobypicture.com)
467 'mobypicture.com/user/*/view/*',
468 'moby.to/*',
469
470 // 23HQ (http://www.23hq.com)
471 '23hq.com/*/photo/*',
472
473 // Cacoo (https://cacoo.com)
474 'cacoo.com/diagrams/*',
475
476 // Dipity (http://www.dipity.com)
477 'dipity.com/*/*/',
478
479 // Roomshare (http://roomshare.jp)
480 'roomshare.jp/post/*',
481 'roomshare.jp/en/post/*',
482
483 // Dailymotion (http://www.dailymotion.com)
484 'dailymotion.com/video/*',
485
486 // Crowd Ranking (http://crowdranking.com)
487 'crowdranking.com/*/*',
488
489 // CircuitLab (https://www.circuitlab.com/)
490 'circuitlab.com/circuit/*',
491
492 // Coub (http://coub.com/)
493 'coub.com/view/*',
494 'coub.com/embed/*',
495
496 // SpeakerDeck (https://speakerdeck.com)
497 'speakerdeck.com/*/*',
498
499 // Instagram (https://instagram.com)
500 'instagram.com/p/*',
501 'instagr.am/p/*',
502
503 // SoundCloud (http://soundcloud.com/)
504 'soundcloud.com/*',
505
506 // On Aol (http://on.aol.com/)
507 'on.aol.com/video/*',
508
509 // Kickstarter (http://www.kickstarter.com)
510 'kickstarter.com/projects/*',
511
512 // Ustream (http://www.ustream.tv)
513 '*.ustream.tv/*',
514 '*.ustream.com/*',
515
516 // Daily Mile (http://www.dailymile.com)
517 'dailymile.com/people/*/entries/*',
518
519 // Sketchfab (http://sketchfab.com)
520 'sketchfab.com/models/*',
521 'sketchfab.com/*/folders/*',
522
523 // Meetup (http://www.meetup.com)
524 'meetup.com/*',
525 'meetu.ps/*',
526
527 // AudioSnaps (http://audiosnaps.com)
528 'audiosnaps.com/k/*',
529
530 // RapidEngage (https://rapidengage.com)
531 'rapidengage.com/s/*',
532
533 // Getty Images (http://www.gettyimages.com/)
534 'gty.im/*',
535
536 // amCharts Live Editor (http://live.amcharts.com/)
537 'live.amcharts.com/*',
538
539 // Infogram (https://infogr.am/)
540 'infogr.am/*',
541
542 // ChartBlocks (http://www.chartblocks.com/)
543 'public.chartblocks.com/c/*',
544
545 // ReleaseWire (http://www.releasewire.com/)
546 'rwire.com/*',
547
548 // ShortNote (https://www.shortnote.jp/)
549 'shortnote.jp/view/notes/*',
550
551 // EgliseInfo (http://egliseinfo.catholique.fr/)
552 'egliseinfo.catholique.fr/*',
553
554 // Silk (http://www.silk.co/)
555 '*.silk.co/explore/*',
556 '*.silk.co/s/embed/*',
557
558 // Twitter
559 'twitter.com/*/status/*',
560
561 // http://bambuser.com
562 'bambuser.com/v/*',
563
564 // https://clyp.it
565 'clyp.it/*',
566
567 // http://www.edocr.com
568 'edocr.com/doc/*',
569
570 // https://gist.github.com
571 'gist.github.com/*',
572
573 // http://issuu.com
574 'issuu.com/*',
575
576 // https://portfolium.com
577 'portfolium.com/*',
578
579 // https://www.reverbnation.com
580 'reverbnation.com/*',
581
582 // http://rutube.ru
583 'rutube.ru/video/*',
584
585 // https://spotify.com/
586 'open.spotify.com/*',
587
588 // http://www.videojug.com
589 'videojug.com/*',
590
591 // https://vine.com
592 'vine.co/*',
593
594 // Facebook
595 'facebook.com/*',
596
597 // Google Maps
598 'google.com/*',
599 'google.com.*/*',
600 'maps.google.com/*',
601
602 // Google Docs
603 'docs.google.com/presentation/*',
604 'docs.google.com/document/*',
605 'docs.google.com/spreadsheets/*',
606 'docs.google.com/forms/*',
607 'docs.google.com/drawings/*'
608 ],
609 patterns = [];
610
611 self.each(urlSchemes, function convertEachURLSchemesToPattern(scheme) {
612 patterns.push(self.convertURLSchemeToPattern(scheme));
613 });
614
615 return patterns;
616 };
617
618 self.addScript = function(source, callback, wrapper) {
619 var doc = self.editor.getDoc();
620
621 if (typeof wrapper === 'undefined' || !wrapper) {
622 wrapper = $(doc.getElementsByTagName('head')[0]);
623 }
624
625 var $script = $(doc.createElement('script'));
626 $script.attr('async', 1);
627
628 if (typeof callback === 'function') {
629 $script.ready(callback);
630 }
631
632 $script.attr('src', source);
633
634 wrapper.append($script);
635 };
636
637 self.addScriptDeclaration = function(wrapper, declaration) {
638 var doc = self.editor.getDoc(),
639 $script = $(doc.createElement('script'));
640
641 $(wrapper).append($script);
642
643 $script.text(declaration);
644 };
645
646 self.addURLsPlaceholder = function(node, url) {
647 var uid = self.makeId();
648
649 var wrapperClasses = ["embedpress_wrapper", "embedpress_placeholder"];
650
651 var shortcodeAttributes = node.value.getShortcodeAttributes($data.EMBEDPRESS_SHORTCODE);
652 var customAttributes = shortcodeAttributes;
653
654 var customClasses = "";
655 if (!!Object.keys(shortcodeAttributes).length) {
656 var specialAttributes = ["class", "href", "data-href"];
657 // Iterates over each attribute of shortcodeAttributes to add the prefix "data-" if missing
658 var dataPrefix = "data-";
659 var prefixedShortcodeAttributes = [];
660 for (var attr in shortcodeAttributes) {
661 if (specialAttributes.indexOf(attr) === -1) {
662 if (attr.indexOf(dataPrefix) !== 0) {
663 prefixedShortcodeAttributes[dataPrefix + attr] = shortcodeAttributes[attr];
664 } else {
665 prefixedShortcodeAttributes[attr] = shortcodeAttributes[attr];
666 }
667 } else {
668 attr = attr.replace(dataPrefix, "");
669 if (attr === "class") {
670 wrapperClasses.push(shortcodeAttributes[attr]);
671 }
672 }
673 }
674
675 shortcodeAttributes = prefixedShortcodeAttributes;
676 prefixedShortcodeAttributes = dataPrefix = null;
677 }
678
679 if (("data-width" in shortcodeAttributes || "data-height" in shortcodeAttributes) && "data-responsive" in shortcodeAttributes) {
680 shortcodeAttributes['data-responsive'] = "false";
681 }
682
683 var wrapper = new self.Node('div', 1);
684 var wrapperSettings = {
685 'class': Array.from(new Set(wrapperClasses)).join(" "),
686 'data-url': url,
687 'data-uid': uid,
688 'id': 'embedpress_wrapper_' + uid,
689 'data-loading-text': 'Loading...'
690 };
691
692 wrapperSettings = $.extend({}, wrapperSettings, shortcodeAttributes);
693
694 wrapper.attr(wrapperSettings);
695
696 var panel = new self.Node('div', 1);
697 panel.attr({
698 'id': 'embedpress_controller_panel_' + uid,
699 'class': 'embedpress_controller_panel embedpress_ignore_mouseout hidden'
700 });
701 wrapper.append(panel);
702
703 function createGhostNode(htmlTag, content) {
704 htmlTag = htmlTag || "span";
705 content = content || "&nbsp;";
706
707 var ghostNode = new self.Node(htmlTag, 1);
708 ghostNode.attr({
709 'class': "hidden"
710 });
711
712 var ghostText = new self.Node('#text', 3);
713 ghostText.value = content;
714 ghostNode.append(ghostText);
715
716 return ghostNode;
717 }
718
719 var editButton = new self.Node('div', 1);
720 editButton.attr({
721 'id': 'embedpress_button_edit_' + uid,
722 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
723 });
724 var editButtonIcon = new self.Node('div', 1);
725 editButtonIcon.attr({
726 'class': 'embedpress-icon-pencil embedpress_ignore_mouseout'
727 });
728 editButtonIcon.append(createGhostNode());
729 editButton.append(editButtonIcon);
730 panel.append(editButton);
731
732 var removeButton = new self.Node('div', 1);
733 removeButton.attr({
734 'id': 'embedpress_button_remove_' + uid,
735 'class': 'embedpress_ignore_mouseout embedpress_controller_button'
736 });
737 var removeButtonIcon = new self.Node('div', 1);
738 removeButtonIcon.attr({
739 'class': 'embedpress-icon-x embedpress_ignore_mouseout'
740 });
741 removeButtonIcon.append(createGhostNode());
742 removeButton.append(removeButtonIcon);
743 panel.append(removeButton);
744
745 node.replace(wrapper);
746
747 // Trigger the timeout which will load the content
748 window.setTimeout(function() {
749 self.parseContentAsync(uid, url, customAttributes);
750 }, 800);
751
752 return wrapper;
753 };
754
755 self.parseContentAsync = function(uid, url, customAttributes) {
756 customAttributes = typeof customAttributes === "undefined" ? {} : customAttributes;
757
758 url = self.decodeEmbedURLSpecialChars(url, true, customAttributes);
759
760 // Get the parsed embed code from the EmbedPress plugin
761 self.getParsedContent(url, function getParsedContentCallback(result) {
762 result.data.content = result.data.content.stripShortcode($data.EMBEDPRESS_SHORTCODE);
763
764 var $wrapper = $(self.getElementInContentById('embedpress_wrapper_' + uid));
765
766 // Parse as DOM element
767 var $content;
768 try {
769 $content = $(result.data.content);
770 } catch(err) {
771 // Fallback to a div, if the result is not a html markup, e.g. a url
772 $content = $('<div>');
773 $content.html(result.data.content);
774 }
775
776 if (!$('iframe', $content).length) {
777 var contentWrapper = $($content).clone();
778 contentWrapper.html('');
779
780 var dom = self.editor.dom;
781
782 $wrapper.removeClass('embedpress_placeholder');
783
784 $wrapper.append(contentWrapper);
785
786 setTimeout(function() {
787 self.editor.undoManager.transact(function() {
788 var iframe = self.editor.getDoc().createElement('iframe');
789 iframe.src = tinymce.Env.ie ? 'javascript:""' : '';
790 iframe.frameBorder = '0';
791 iframe.allowTransparency = 'true';
792 iframe.scrolling = 'no';
793 iframe.class = "wpview-sandbox";
794 iframe.style.width = (customAttributes.width ? customAttributes.width +'px' : '100%');
795
796 dom.add(contentWrapper, iframe);
797
798 var iframeWindow = iframe.contentWindow;
799 var iframeDoc = iframeWindow.document;
800
801 $(iframe).load(function() {
802 if (customAttributes.height) {
803 iframe.height = customAttributes.height;
804 iframe.style.height = customAttributes.height +'px';
805 } else {
806 iframe.height = iframeDoc.body.scrollHeight;
807 }
808 });
809
810 iframeDoc.open();
811 iframeDoc.write(
812 '<!DOCTYPE html>'+
813 '<html>'+
814 '<head>'+
815 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'+
816 '<style>'+
817 'html {'+
818 'background: transparent;'+
819 'padding: 0;'+
820 'margin: 0;'+
821 '}'+
822 'body#wpview-iframe-sandbox {'+
823 'background: transparent;'+
824 'padding: 1px 0 !important;'+
825 'margin: -1px 0 0 !important;'+
826 '}'+
827 'body#wpview-iframe-sandbox:before,'+
828 'body#wpview-iframe-sandbox:after {'+
829 'display: none;'+
830 'content: "";'+
831 '}'+
832 '</style>'+
833 '</head>'+
834 '<body id="wpview-iframe-sandbox" class="'+ self.editor.getBody().className +'">'+
835 $content.html() +
836 '</body>'+
837 '</html>'
838 );
839 iframeDoc.close();
840 });
841 }, 50);
842 } else {
843 $wrapper.removeClass('embedpress_placeholder');
844
845 self.appendElementsIntoWrapper($content, $wrapper);
846 }
847 });
848 };
849
850 self.appendElementsIntoWrapper = function(elementsList, wrapper) {
851 if (elementsList.length > 0) {
852 $.each(elementsList, function appendElementIntoWrapper(elementIndex, element) {
853 // Check if the element is a script and do not add it now (if added here it wouldn't be executed)
854 if (element.tagName.toLowerCase() !== 'script') {
855 wrapper.append($(element));
856
857 if (element.tagName.toLowerCase() === 'iframe') {
858 $(element).ready(function() {
859 window.setTimeout(function() {
860 $.each(self.editor.dom.select('div.embedpress_wrapper iframe'), function(elementIndex, iframe) {
861 self.fixIframeSize(iframe);
862 });
863 }, 300);
864 });
865 } else if (element.tagName.toLowerCase() === "div") {
866 if ($('img', $(element)).length || $('blockquote', wrapper).length) {
867 // This ensures that the embed wrapper have the same width as its content
868 $($(element).parents('.embedpress_wrapper').get(0)).addClass('dynamic-width');
869 }
870
871 $(element).css('max-width', $($(element).parents('body').get(0)).width());
872 }
873 }
874
875 self.loadAsyncDynamicJsCodeFromElement(element, wrapper);
876 });
877 }
878
879 return wrapper;
880 };
881
882 self.encodeEmbedURLSpecialChars = function(content) {
883 if (content.match(SHORTCODE_REGEXP)) {
884 var subject = content.replace(SHORTCODE_REGEXP, '');
885
886 if (!subject.isValidUrl()) {
887 return content;
888 }
889
890 content = subject;
891 subject = null;
892 }
893
894 // Bypass the autolink plugin, avoiding to have the url converted to a link automatically
895 content = content.replace(/http(s?)\:\/\//i, 'embedpress$1://');
896
897 // Bypass the autolink plugin, avoiding to have some urls with @ being treated as email address (e.g. GMaps)
898 content = content.replace('@', '::__at__::').trim();
899
900 return content;
901 };
902
903 self.decodeEmbedURLSpecialChars = function(content, applyShortcode, attributes) {
904 var encodingRegexpRule = /embedpress(s?):\/\//;
905 applyShortcode = (typeof applyShortcode === "undefined") ? true : applyShortcode;
906 attributes = (typeof attributes === "undefined") ? {} : attributes;
907
908 var isEncoded = content.match(encodingRegexpRule);
909
910 // Restore http[s] in the url (converted to bypass autolink plugin)
911 content = content.replace(/embedpress(s?):\/\//, 'http$1://');
912 content = content.replace('::__at__::', '@').trim();
913
914 if ("class" in attributes) {
915 var classesList = attributes.class.split(/\s/g);
916 var shouldRemoveDynamicWidthClass = false;
917 for (var classIndex = 0; classIndex < classesList.length; classIndex++) {
918 if (classesList[classIndex] === "dynamic-width") {
919 shouldRemoveDynamicWidthClass = classIndex;
920 break;
921 }
922 }
923
924 if (shouldRemoveDynamicWidthClass !== false) {
925 classesList.splice(shouldRemoveDynamicWidthClass, 1);
926
927 if (classesList.length === 0) {
928 delete attributes.class;
929 }
930
931 attributes.class = classesList.join(" ");
932 }
933
934 shouldRemoveDynamicWidthClass = classesList = classIndex = null;
935 }
936
937 if (isEncoded && applyShortcode) {
938 var shortcode = '[' + $data.EMBEDPRESS_SHORTCODE;
939 if (!!Object.keys(attributes).length) {
940 var attrValue;
941
942 for (var attrName in attributes) {
943 attrValue = attributes[attrName];
944
945 // Prevent `class` property being empty an treated as a boolean param
946 if (attrName.toLowerCase() === "class" && !attrValue.length) {
947 continue;
948 }
949 else {
950 if (attrValue.isBoolean()) {
951 shortcode += " ";
952 if (attrValue.isFalse()) {
953 shortcode += "!";
954 }
955
956 shortcode += attrName
957 } else {
958 shortcode += ' '+ attrName +'="'+ attrValue +'"';
959 }
960 }
961 }
962 attrValue = attrName = null;
963 }
964
965 content = shortcode + ']' + content + '[/' + $data.EMBEDPRESS_SHORTCODE + ']';
966 }
967
968 return content;
969 };
970
971 /**
972 * Method executed after find the editor. It will make additional
973 * configurations and add the content's stylesheets for the preview
974 *
975 * @return void
976 */
977 self.onFindEditor = function() {
978 self.each = tinymce.each;
979 self.extend = tinymce.extend;
980 self.JSON = tinymce.util.JSON;
981 self.Node = tinymce.html.Node;
982
983 function onFindEditorCallback() {
984 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/font.css?' + self.params.versionUID);
985 self.addStylesheet(PLG_SYSTEM_ASSETS_CSS_PATH + '/preview.css?' + self.params.versionUID);
986 self.addStylesheet(PLG_CONTENT_ASSETS_CSS_PATH + '/embedpress.css?' + self.params.versionUID);
987 self.addEvent('paste', self.editor, self.onPaste);
988 self.addEvent('nodechange', self.editor, self.onNodeChange);
989 self.addEvent('keydown', self.editor, self.onKeyDown);
990
991 self.addEvent('undo', self.editor, self.onUndo); // TinyMCE
992 self.addEvent('undo', self.editor.undoManager, self.onUndo); // JCE
993
994 // Add the node filter that will convert the url into the preview box for the embed code
995 // @todo: Recognize <a> tags as well
996 self.editor.parser.addNodeFilter('#text', function addNodeFilterIntoParser(nodes, arg) {
997 self.each(nodes, function eachNodeInParser(node) {
998 var subject = node.value;
999 if (!subject.isValidUrl()) {
1000 if (!subject.match(SHORTCODE_REGEXP)) {
1001 return;
1002 }
1003 }
1004
1005 subject = self.decodeEmbedURLSpecialChars(subject);
1006 if (!subject.isValidUrl()) {
1007 if (!subject.match(SHORTCODE_REGEXP)) {
1008 return;
1009 }
1010 }
1011
1012 subject = node.value.stripShortcode($data.EMBEDPRESS_SHORTCODE).trim();
1013
1014 // These patterns need to have groups for the pre and post texts
1015 var patterns = self.getProvidersURLPatterns();
1016
1017 self.each(patterns, function eachPatternForNodeFilterInParser(pattern) {
1018 var regex = new RegExp(pattern),
1019 matches,
1020 value;
1021
1022 value = self.decodeEmbedURLSpecialChars(subject).trim();
1023
1024 matches = value.match(regex);
1025 if (matches !== null && !!matches.length) {
1026 var preText = matches[1];
1027 var url = self.encodeEmbedURLSpecialChars(matches[2]);
1028 var postText = matches[3];
1029 var wrapper = self.addURLsPlaceholder(node, url);
1030
1031 // Add the pre text if exists
1032 var text;
1033 if (preText !== '') {
1034 text = new self.Node('#text', 3);
1035 text.value = preText.trim();
1036
1037 // Insert before
1038 wrapper.parent.insert(text, wrapper, true);
1039 }
1040
1041 // Add the post text if exists
1042 if (postText !== '') {
1043 text = new self.Node('#text', 3);
1044 text.value = postText.trim();
1045
1046 // Insert after
1047 wrapper.parent.insert(text, wrapper, false);
1048 }
1049 }
1050 });
1051 });
1052 });
1053
1054 // Add the filter that will convert the preview box/embed code back to the raw url
1055 self.editor.serializer.addNodeFilter('div', function addNodeFilterIntoSerializer(nodes, arg) {
1056 self.each(nodes, function eachNodeInSerializer(node) {
1057 var nodeClasses = (node.attributes.map.class || "").split(' ');
1058 var wrapperFactoryClasses = ["embedpress_wrapper", "embedpress_placeholder"];
1059
1060 var isWrapped = nodeClasses.filter(function(n) {
1061 return wrapperFactoryClasses.indexOf(n) != -1;
1062 }).length > 0;
1063
1064 if (isWrapped) {
1065 var factoryAttributes = ["id", "style", "data-loading-text", "data-uid", "data-url"];
1066 var customAttributes = {};
1067 var dataPrefix = "data-";
1068 for (var attr in node.attributes.map) {
1069 if (attr.toLowerCase() !== "class") {
1070 if (factoryAttributes.indexOf(attr) < 0) {
1071 // Remove the "data-" prefix for more readability
1072 customAttributes[attr.replace(dataPrefix, "")] = node.attributes.map[attr];
1073 }
1074 } else {
1075 var customClasses = [];
1076 for (var wrapperClassIndex in nodeClasses) {
1077 var wrapperClass = nodeClasses[wrapperClassIndex];
1078 if (wrapperFactoryClasses.indexOf(wrapperClass) === -1) {
1079 customClasses.push(wrapperClass);
1080 }
1081 }
1082
1083 if (!!customClasses.length) {
1084 customAttributes.class = customClasses.join(" ");
1085 }
1086 }
1087 }
1088
1089 var text = new self.Node('#text', 3);
1090 text.value = self.decodeEmbedURLSpecialChars(node.attributes.map['data-url'].trim(), true, customAttributes);
1091
1092 node.replace(text);
1093
1094 // @todo: Remove/avoid to add empty paragraphs before and after the text every time we run this
1095 }
1096 });
1097 });
1098
1099 // Add event to reconfigure wrappers every time the content is loaded
1100 tinymce.each(tinymce.editors, function onEachEditor(editor) {
1101 self.addEvent('loadContent', editor, function onInitEditor(ed) {
1102 self.configureWrappers();
1103 });
1104 });
1105
1106 // Add the edit form
1107
1108 // @todo: This is needed only for JCE, to fix the img placeholder. Try to find out a better approach to avoid the placeholder blink
1109 window.setTimeout(
1110 function afterTimeoutOnFindEditor() {
1111 /*
1112 * This is required because after load/refresh the page, the
1113 * onLoadContent is not being triggered automatically, so
1114 * we force the event
1115 */
1116 self.editor.load();
1117 },
1118 // If in JCE the user see the placeholder (img) instead of the iframe after load/refresh the pagr, this time is too short
1119 500
1120 );
1121 }
1122
1123 // Let's make sure the inner doc has been fully loaded first.
1124 var checkTimesLimit = 100;
1125 var checkIndex = 0;
1126 var statusCheckerInterval = setInterval(function() {
1127 if (checkIndex === checkTimesLimit) {
1128 clearInterval(statusCheckerInterval);
1129 alert('For some reason TinyMCE was not fully loaded yet. Please, refresh the page and try again.');
1130 } else {
1131 var doc = self.editor.getDoc();
1132 if (doc) {
1133 clearInterval(statusCheckerInterval);
1134 onFindEditorCallback();
1135 } else {
1136 checkIndex++;
1137 }
1138 }
1139 }, 250);
1140 };
1141
1142 self.fixIframeSize = function(iframe) {
1143 var maxWidth = 480;
1144 if ($(iframe).width() > maxWidth && !$(iframe).data('size-fixed')) {
1145 var ratio = $(iframe).height() / $(iframe).width();
1146 $(iframe).width(maxWidth);
1147 $(iframe).height(maxWidth * ratio);
1148 $(iframe).css('max-width', maxWidth);
1149 $(iframe).attr('max-width', maxWidth);
1150
1151 $(iframe).data('size-fixed', true);
1152 }
1153 }
1154
1155 /**
1156 * Function triggered on mouse enter the wrapper
1157 *
1158 * @param object e The event
1159 * @return void
1160 */
1161 self.onMouseEnter = function(e) {
1162 self.displayPreviewControllerPanel($(e.currentTarget));
1163 };
1164
1165 /**
1166 * Function triggered on mouse get out of the wrapper
1167 *
1168 * @param object e The event
1169 * @return void
1170 */
1171 self.onMouseOut = function(e) {
1172 // Check if the destiny is not a child element
1173 // Chrome
1174 if (self.isDefined(e.toElement)) {
1175 if (e.toElement.parentElement == e.fromElement
1176 || $(e.toElement).hasClass('embedpress_ignore_mouseout')
1177 ) {
1178 return false;
1179 }
1180 }
1181
1182 // Firefox
1183 if (self.isDefined(e.relatedTarget)) {
1184 if ($(e.relatedTarget).hasClass('embedpress_ignore_mouseout')) {
1185 return false;
1186 }
1187 }
1188
1189 self.hidePreviewControllerPanel();
1190 };
1191
1192 /**
1193 * Function called on paste event in the editor
1194 *
1195 * @param object e The event
1196 * @return void
1197 */
1198 self.onPaste = function(e, b) {
1199 var event;
1200
1201 // Prevent default paste behavior. We have 2 arguments because the difference between JCE and TinyMCE.
1202 // Sometimes, the argument e is the editor, instead of the event
1203 if (e.preventDefault) {
1204 event = e;
1205 } else {
1206 event = b;
1207 }
1208
1209 // Check for clipboard data in various places for cross-browser compatibility
1210 // Get that data as text
1211 var content = ((event.originalEvent || event).clipboardData || window.clipboardData).getData('Text');
1212
1213 // Check if the pasted content has a recognized embed url pattern
1214 var patterns = self.getProvidersURLPatterns();
1215
1216 self.each(patterns, function eachPatternForOnPaste(pattern) {
1217 var regex = new RegExp(pattern),
1218 matches = content.match(regex),
1219 url;
1220
1221 if (matches !== null && !!matches.length) {
1222 event.preventDefault();
1223
1224 content += '<span>&nbsp;</span>'; // This assures that the cursor are positioned after the embed
1225
1226 // Let TinyMCE do the heavy lifting for inserting that content into the self.editor
1227 // We cancel the default behavior and insert using command to trigger the node change and the parser
1228 self.editor.execCommand('mceInsertContent', false, content);
1229
1230 self.configureWrappers();
1231 }
1232 });
1233 };
1234
1235 /**
1236 * Method trigered on every node change, to detect new lines. It will
1237 * try to fix a default behavior for some editors of clone the parent
1238 * element when adding a line break. This will clone the embed wrapper
1239 * if we set the cursor after a preview wrapper and hit enter.
1240 *
1241 * @param object e The event
1242 * @return void
1243 */
1244 self.onNodeChange = function(e) {
1245 // Fix the clone parent on break lines issue
1246 // Check if a line break was added
1247 if (e.element.tagName === 'BR') {
1248 // Check one of the parent elements is a clonned embed wrapper
1249 if (e.parents.length > 0) {
1250 $.each(e.parents, function(index, parent) {
1251 if ($(parent).hasClass('embedpress_wrapper')) {
1252 // Remove the cloned wrapper and replace with a 'br' tag
1253 $(parent).replaceWith($('<br>'));
1254 }
1255 });
1256 }
1257 } else if (e.element.tagName === "IFRAME") {
1258 if (e.parents.length > 0) {
1259 $.each(e.parents, function(index, parent) {
1260 parent = $(parent);
1261 if (parent.hasClass('embedpress_wrapper')) {
1262 var wrapper = $('.embedpress-wrapper', parent);
1263 if (wrapper.length > 1) {
1264 wrapper.get(0).remove();
1265 }
1266 }
1267 });
1268 }
1269 }
1270 };
1271
1272 self.onKeyDown = function(e) {
1273 var node = self.editor.selection.getNode();
1274
1275 if (e.keyCode == 8 || e.keyCode == 46) {
1276 if (node.nodeName.toLowerCase() === 'p') {
1277 var children = $(node).children();
1278 if (children.length > 0) {
1279 $.each(children, function() {
1280 // On delete, make sure to remove the wrapper and children, not only the wrapper
1281 if ($(this).hasClass('embedpress_wrapper') || $(this).hasClass('embedpress_ignore_mouseout')) {
1282 $(this).remove();
1283
1284 self.editor.focus();
1285 }
1286 });
1287 }
1288 }
1289 } else {
1290 // Ignore the arrows keys
1291 var arrowsKeyCodes = [37, 38, 39, 40];
1292 if (arrowsKeyCodes.indexOf(e.keyCode) == -1) {
1293
1294 // Check if we are inside a preview wrapper
1295 if ($(node).hasClass('embedpress_wrapper') || $(node).hasClass('embedpress_ignore_mouseout')) {
1296 // Avoid delete the wrapper or block line break if we are inside the wrapper
1297 if (e.keyCode == 13) {
1298 wrapper = $(self.getWrapperFromChild(node));
1299 if (wrapper.length > 0) {
1300 // Creates a temporary element which will be inserted after the wrapper
1301 var tmpId = '__embedpress__tmp_' + self.makeId();
1302 wrapper.after($('<span id="' + tmpId + '"></span>'));
1303 // Get and select the temporary element
1304 var span = self.editor.dom.select('span#' + tmpId)[0];
1305 self.editor.selection.select(span);
1306 // Remove the temporary element
1307 $(span).remove();
1308 }
1309
1310 return true;
1311 } else {
1312 // If we are inside the embed preview, ignore any key to avoid edition
1313 return self.cancelEvent(e);
1314 }
1315 }
1316 }
1317 }
1318
1319 return true;
1320 }
1321
1322 self.getWrapperFromChild = function(element) {
1323 // Is the wrapper
1324 if ($(element).hasClass('embedpress_wrapper')) {
1325 return element;
1326 } else {
1327 var $parent = $(element).parent();
1328
1329 if ($parent.length > 0) {
1330 return self.getWrapperFromChild($parent[0]);
1331 }
1332 }
1333
1334 return false;
1335 };
1336
1337 self.onUndo = function(e) {
1338 // Force re-render everything
1339 self.editor.load();
1340 };
1341
1342 self.cancelEvent = function(e) {
1343 e.preventDefault();
1344 e.stopPropagation();
1345 self.editor.dom.events.cancel();
1346
1347 return false;
1348 };
1349
1350 /**
1351 * Method executed when the edit button is clicked. It will display
1352 * a field with the current url, to update the current embed's source
1353 * url.
1354 *
1355 * @param Object e The event
1356 * @return void
1357 */
1358 self.onClickEditButton = function(e) {
1359 // Prevent edition of the panel
1360 self.cancelEvent(e);
1361
1362 self.activeWrapperForModal = self.activeWrapper;
1363
1364 var $wrapper = self.activeWrapperForModal;
1365 var wrapperUid = $wrapper.prop('id').replace("embedpress_wrapper_", "");
1366
1367 var customAttributes = {};
1368
1369 var embedInnerWrapper = $('.embedpress-wrapper', $wrapper);
1370 var embedItem = $('iframe', $wrapper);
1371 if (!embedItem.length) {
1372 embedItem = null;
1373 }
1374
1375 $.each(embedInnerWrapper.attributes, function() {
1376 if (this.specified) {
1377 if (this.name !== "class") {
1378 customAttributes[this.name.replace('data-', "").toLowerCase()] = this.value;
1379 }
1380 }
1381 });
1382
1383 var embedWidth = (((embedItem && embedItem.width()) || embedInnerWrapper.data('width')) || embedInnerWrapper.width()) || "";
1384 var embedHeight = (((embedItem && embedItem.height()) || embedInnerWrapper.data('height')) || embedInnerWrapper.height()) || "";
1385
1386 embedItem = embedInnerWrapper = null;
1387
1388 $('<div class="loader-indicator"><i class="embedpress-icon-reload"></i></div>').appendTo($wrapper);
1389
1390 setTimeout(function() {
1391 $.ajax({
1392 type: "GET",
1393 url: "admin-ajax.php",
1394 data: {
1395 action: "embedpress_get_embed_url_info",
1396 url: self.decodeEmbedURLSpecialChars($wrapper.data('url'), false)
1397 },
1398 beforeSend: function(request, requestSettings) {
1399 $('.loader-indicator', $wrapper).addClass('is-loading');
1400 },
1401 success: function(response) {
1402 if (!response) {
1403 bootbox.alert('Unable to get a valid response from the server.');
1404 return;
1405 }
1406
1407 if (response.canBeResponsive) {
1408 var embedShouldBeResponsive = true;
1409 if ("width" in customAttributes || "height" in customAttributes) {
1410 embedShouldBeResponsive = false;
1411 } else if ("responsive" in customAttributes && customAttributes['responsive'].isFalse()) {
1412 embedShouldBeResponsive = false;
1413 }
1414 }
1415
1416 bootbox.dialog({
1417 className: "embedpress-modal",
1418 title: "Editing Embed properties",
1419 message: '<form id="form-'+ wrapperUid +'" embedpress>'+
1420 '<div class="row">'+
1421 '<div class="col-md-12">'+
1422 '<div class="form-group">'+
1423 '<label for="input-url-'+ wrapperUid +'">Url</label>'+
1424 '<input class="form-control" type="url" id="input-url-'+ wrapperUid +'" value="'+ self.decodeEmbedURLSpecialChars($wrapper.data('url'), false) +'">'+
1425 '</div>'+
1426 '</div>'+
1427 '</div>'+
1428 '<div class="row">'+
1429 (response.canBeResponsive ?
1430 '<div class="col-md-12">'+
1431 '<label>Responsive</label>'+
1432 '<div class="form-group">'+
1433 '<label class="radio-inline">'+
1434 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-1-'+ wrapperUid +'" value="1"'+ (embedShouldBeResponsive ? ' checked' : '') +'> Yes'+
1435 '</label>'+
1436 '<label class="radio-inline">'+
1437 '<input type="radio" name="input-responsive-'+ wrapperUid +'" id="input-responsive-0-'+ wrapperUid +'" value="0"'+ (!embedShouldBeResponsive ? ' checked' : '') +'> No'+
1438 '</label>'+
1439 '</div>'+
1440 '</div>' : '')+
1441 '<div class="col-md-6">'+
1442 '<div class="form-group">'+
1443 '<label for="input-width-'+ wrapperUid +'">Width</label>'+
1444 '<input class="form-control" type="integer" id="input-width-'+ wrapperUid +'" value="'+ embedWidth +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1445 '</div>'+
1446 '</div>'+
1447 '<div class="col-md-6">'+
1448 '<div class="form-group">'+
1449 '<label for="input-height-'+ wrapperUid +'">Height</label>'+
1450 '<input class="form-control" type="integer" id="input-height-'+ wrapperUid +'" value="'+ embedHeight +'"'+ (embedShouldBeResponsive ? ' disabled' : '') +'>'+
1451 '</div>'+
1452 '</div>'+
1453 '</div>'+
1454 '</form>',
1455 buttons: {
1456 danger: {
1457 label: "Cancel",
1458 className: "btn-default",
1459 callback: function() {
1460 // do nothing
1461 self.activeWrapperForModal = null;
1462 }
1463 },
1464 success: {
1465 label: "Save",
1466 className: "btn-primary",
1467 callback: function() {
1468 var $wrapper = self.activeWrapperForModal;
1469
1470 // Select the current wrapper as a base for the new element
1471 self.editor.focus();
1472 self.editor.selection.select($wrapper[0]);
1473
1474 $wrapper.children().remove();
1475 $wrapper.remove();
1476
1477 if (response.canBeResponsive) {
1478 if ($('#form-'+ wrapperUid +' input[name="input-responsive-'+ wrapperUid +'"]:checked').val().isFalse()) {
1479 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1480 if (parseInt(embedCustomWidth) > 0) {
1481 customAttributes['width'] = embedCustomWidth;
1482 }
1483
1484 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1485 if (parseInt(embedCustomHeight) > 0) {
1486 customAttributes['height'] = embedCustomHeight;
1487 }
1488
1489 customAttributes['responsive'] = "false";
1490 } else {
1491 delete customAttributes['width'];
1492 delete customAttributes['height'];
1493
1494 customAttributes['responsive'] = "true";
1495 }
1496 } else {
1497 var embedCustomWidth = $('#input-width-'+ wrapperUid).val();
1498 if (parseInt(embedCustomWidth) > 0) {
1499 customAttributes['width'] = embedCustomWidth;
1500 }
1501
1502 var embedCustomHeight = $('#input-height-'+ wrapperUid).val();
1503 if (parseInt(embedCustomHeight) > 0) {
1504 customAttributes['height'] = embedCustomHeight;
1505 }
1506 }
1507
1508 var customAttributesList = [];
1509 if (!!Object.keys(customAttributes).length) {
1510 for (var attrName in customAttributes) {
1511 customAttributesList.push(attrName + '="' + customAttributes[attrName] + '"');
1512 }
1513 }
1514
1515 var shortcode = '['+ $data.EMBEDPRESS_SHORTCODE + (customAttributesList.length > 0 ? " "+ customAttributesList.join(" ") : "") +']'+ $('#input-url-'+ wrapperUid).val() +'[/'+ $data.EMBEDPRESS_SHORTCODE +']';
1516 // We do not directly replace the node because it was causing a bug on a second edit attempt
1517 self.editor.execCommand('mceInsertContent', false, shortcode);
1518
1519 self.configureWrappers();
1520 }
1521 }
1522 }
1523 });
1524
1525 $('form[embedpress]').on('change', 'input[type="radio"]', function(e) {
1526 var self = $(this);
1527 var form = self.parents('form[embedpress]');
1528
1529 $('input[type="integer"]', form).prop('disabled', self.val().isTrue());
1530 });
1531 },
1532 complete: function(request, textStatus) {
1533 $('.loader-indicator', $wrapper).removeClass('is-loading');
1534
1535 setTimeout(function() {
1536 $('.loader-indicator', $wrapper).remove();
1537 }, 350);
1538 },
1539 dataType: "json",
1540 async: true
1541 });
1542 }, 200);
1543
1544 return false;
1545 };
1546
1547 /**
1548 * Method executed when the remove button is clicked. It will remove
1549 * the preview and embed code, adding a mark to ignore the url
1550 *
1551 * @param Object e The event
1552 * @return void
1553 */
1554 self.onClickRemoveButton = function(e) {
1555 // Prevent edition of the panel
1556 self.cancelEvent(e);
1557
1558 var $wrapper = self.activeWrapper;
1559
1560 $wrapper.children().remove();
1561 $wrapper.remove();
1562
1563 return false;
1564 };
1565
1566 self.recursivelyAddClass = function(element, className) {
1567 $(element).children().each(function(index, child) {
1568 $(child).addClass(className);
1569
1570 var grandChild = $(child).children();
1571 if (grandChild.length > 0) {
1572 self.recursivelyAddClass(child, className)
1573 }
1574 });
1575 };
1576
1577 self.setInterval = function(callback, time, timeout) {
1578 var elapsed = 0;
1579 var iteraction = 0;
1580
1581 var interval = window.setInterval(function() {
1582 elapsed += time;
1583 iteraction++;
1584
1585 if (elapsed <= timeout) {
1586 callback(iteraction, elapsed);
1587 } else {
1588 self.stopInterval(interval);
1589 }
1590 }, time);
1591
1592 return interval;
1593 };
1594
1595 self.stopInterval = function(interval) {
1596 window.clearInterval(interval);
1597 interval = null;
1598 };
1599
1600 /**
1601 * Configure unconfigured embed wrappers, adding events and css
1602 * @return void
1603 */
1604 self.configureWrappers = function() {
1605 window.setTimeout(
1606 function configureWrappersTimeOut() {
1607 var doc = self.editor.getDoc(),
1608 total = 0,
1609 $wrapper = null,
1610 $iframe = null;
1611
1612 // Get all the wrappers
1613 var wrappers = doc.getElementsByClassName('embedpress_wrapper');
1614 total = wrappers.length;
1615 if (total > 0) {
1616 for (var i = 0; i < total; i++) {
1617 $wrapper = $(wrappers[i]);
1618
1619 // Check if the wrapper wasn't already configured
1620 if ($wrapper.data('configured') != true) {
1621 // A timeout was set to avoid block the content loading
1622 window.setTimeout(function() {
1623 // @todo: Check if we need a limit of levels to avoid use too much resources
1624 self.recursivelyAddClass($wrapper, 'embedpress_ignore_mouseout');
1625 }, 500);
1626
1627 // Fix the wrapper size. Wait until find the child iframe. L
1628 var interval = self.setInterval(function(iteraction) {
1629 var $childIframes = $wrapper.find('iframe');
1630
1631 if ($childIframes.length > 0) {
1632 $.each($childIframes, function(index, iframe) {
1633 // Facebook has more than one iframe, we need to ignore the Cross Domain Iframes
1634 if ($(iframe).attr('id') !== 'fb_xdm_frame_https'
1635 && $(iframe).attr('id') !== 'fb_xdm_frame_http'
1636 ) {
1637 $wrapper.css('width', $(iframe).width() + 'px');
1638 self.stopInterval(interval);
1639 }
1640 });
1641 }
1642 }, 500, 8000);
1643
1644 $wrapper.on('mouseenter', self.onMouseEnter);
1645 $wrapper.on('mouseout', self.onMouseOut);
1646 $wrapper.data('configured', true);
1647 }
1648 }
1649 }
1650 },
1651 200
1652 );
1653 };
1654
1655 /**
1656 * Hide the controller panel
1657 *
1658 * @return void
1659 */
1660 self.hidePreviewControllerPanel = function() {
1661 if (self.controllerPanelIsActive()) {
1662 $(self.activeControllerPanel).addClass('hidden');
1663 self.activeControllerPanel = null;
1664 self.activeWrapper = null;
1665 }
1666 };
1667
1668 /**
1669 * Get an element by id in the editor's content
1670 *
1671 * @param String id The element id
1672 * @return Element The found element or null, wrapped by jQuery
1673 */
1674 self.getElementInContentById = function(id) {
1675 var doc = self.editor.getDoc();
1676
1677 return $(doc.getElementById(id));
1678 };
1679
1680 /**
1681 * Show the controller panel
1682 *
1683 * @param element $wrapper The wrapper which will be activate
1684 * @return void
1685 */
1686 self.displayPreviewControllerPanel = function($wrapper) {
1687 if (self.controllerPanelIsActive() && $wrapper !== self.activeWrapper) {
1688 self.hidePreviewControllerPanel();
1689 }
1690
1691 if (!self.controllerPanelIsActive()) {
1692 var uid = $wrapper.data('uid');
1693 var $panel = self.getElementInContentById('embedpress_controller_panel_' + uid);
1694
1695 if (!$panel.data('event-set')) {
1696 var $editButton = self.getElementInContentById('embedpress_button_edit_' + uid);
1697 var $removeButton = self.getElementInContentById('embedpress_button_remove_' + uid);
1698
1699 self.addEvent('mousedown', $editButton, self.onClickEditButton);
1700 self.addEvent('mousedown', $removeButton, self.onClickRemoveButton);
1701
1702 // Prevent the action of set cursor into the panel after click
1703 self.addEvent('mousedown', $panel, self.cancelEvent);
1704
1705 $panel.data('event-set', true);
1706 }
1707
1708 // Update the position of the control bar
1709 var next = $panel.next()[0];
1710 if (typeof next !== 'undefined') {
1711 if (next.nodeName.toLowerCase() === 'iframe') {
1712 $panel.css('left', ($(next).width() / 2));
1713 }
1714 }
1715
1716 // Show the bar
1717 $panel.removeClass('hidden');
1718
1719 self.activeControllerPanel = $panel;
1720 self.activeWrapper = $wrapper;
1721 }
1722 };
1723 };
1724
1725 if (!window.EmbedPressPreview) {
1726 window.EmbedPressPreview = new EmbedPressPreview();
1727 }
1728
1729 window.EmbedPressPreview.init($data.previewSettings);
1730 });
1731 })(window, jQuery, String, $data);
1732