PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 4.6.3
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v4.6.3
4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / EmbedPress / ThirdParty / Googlecalendar / assets / js / main.js
embedpress / EmbedPress / ThirdParty / Googlecalendar / assets / js Last commit date
main.js 3 months ago
main.js
541 lines
1 (function(win) {
2 jQuery(document).ready(function () {
3
4 window.fullCalendars = [];
5
6 var int_reg = /^\d+$/;
7
8 function underscoreToUpper(s) {
9 // event_limit ==> eventLimit
10 return s.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
11 }
12
13 // Because attributes are always strings, we need to cast them to appropriate types.
14 function castAttrValue(value, defaultValue) {
15 if (value === 'true') return true;
16 if (value === 'false') return false;
17 if (int_reg.test(value)) {
18 return parseInt(value, 10);
19 }
20 if (!value && typeof defaultValue !== "undefined") {
21 return defaultValue;
22 }
23 return value;
24 }
25
26 function getConfigBackgroundColor(config) {
27 if ("eventBackgroundColor" in config) {
28 return config.eventBackgroundColor;
29 }
30 if ("eventColor" in config) {
31 return config.eventColor;
32 }
33 return false;
34 }
35
36 function padDatePart(d) {
37 if (d < 10) return "0" + d.toString();
38 return d;
39 }
40
41 function dateFormat(date) {
42 return date.getFullYear() + "-" + padDatePart(date.getMonth() + 1) + "-" + padDatePart(date.getDate());
43 }
44
45 function castObjectAttributes(obj) {
46 Object.keys(obj).forEach(function(key) {
47 if (obj[key]) {
48 switch (typeof obj[key]) {
49 case 'string':
50 obj[key] = castAttrValue(obj[key]);
51 break;
52 case 'object':
53 if (obj[key].constructor === Object) {
54 castObjectAttributes(obj[key]);
55 }
56 break;
57 }
58 }
59 });
60 }
61
62
63 // Per-wrapper init isolated into a function so the Gutenberg block editor
64 // can call window.epgcInitWrappers() after ServerSideRender mounts new
65 // markup (jQuery(document).ready fires once; this re-runs on demand).
66 function initWrapper(calendarWrapper, calendarCounter) {
67
68 // Bridge LocalizationManager's `embedpressCalendarData` (new naming)
69 // to the legacy `epgc_object` shape this file was written against.
70 // Falls back to data-* attributes on the wrapper when an optimizer
71 // plugin strips the inline wp_localize_script block.
72 var lm = window.embedpressCalendarData || {};
73 var t = lm.translations || {};
74 var epgc_object = {
75 nonce: lm.nonce || calendarWrapper.getAttribute('data-nonce') || '',
76 ajax_url: lm.ajaxUrl || calendarWrapper.getAttribute('data-ajaxurl') || window.ajaxurl || '',
77 trans: {
78 loading: t.loading || 'Loading',
79 all_day: t.allDay || 'All day',
80 created_by: t.createdBy || 'Created by',
81 go_to_event: t.goToEvent || 'Go to event',
82 unknown_error: t.unknownError || 'Unknown error',
83 request_error: t.requestError || 'Request error',
84 },
85 };
86
87 var errorEl = window.document.createElement("div");
88 errorEl.className = "epgc-error-el";
89 var loadingEl = window.document.createElement("div");
90 loadingEl.className = "epgc-loading-el";
91
92 var currentAllEvents = null;
93 var fullCalendar = null;
94 var $calendar = calendarWrapper.querySelector('.epgc-calendar');
95 var $calendarFilter = calendarWrapper.querySelector('.epgc-calendar-filter');
96 var errorAndLoadingParent = null; // will be set by FullCalendar, so is not available now.
97
98 var selectedCalIds = null;
99 var allCalendars = null;
100
101 // Always present, gets set in PHP file.
102 // Note: make sure you use the same defaults as get set in the PHP file!
103 var isPublic = castAttrValue($calendar.getAttribute('data-public'), false);
104 var filter = castAttrValue($calendar.getAttribute('data-filter'));
105 var showEventPopup = castAttrValue($calendar.getAttribute('data-eventpopup'), true);
106 var showEventLink = castAttrValue($calendar.getAttribute('data-eventlink'), false);
107 var hidePassed = castAttrValue($calendar.getAttribute('data-hidepassed'), false);
108 var hideFuture = castAttrValue($calendar.getAttribute('data-hidefuture'), false);
109 var showEventDescription = castAttrValue($calendar.getAttribute('data-eventdescription'), false);
110 var showEventLocation = castAttrValue($calendar.getAttribute('data-eventlocation'), false);
111 var showEventAttendees = castAttrValue($calendar.getAttribute('data-eventattendees'), false);
112 var showEventAttachments = castAttrValue($calendar.getAttribute('data-eventattachments'), false);
113 var showEventCreator = castAttrValue($calendar.getAttribute('data-eventcreator'), false);
114 var showEventCalendarname = castAttrValue($calendar.getAttribute('data-eventcalendarname'), false);
115
116 var uncheckedCalendarIds = $calendarFilter && $calendarFilter.getAttribute("data-uncheckedcalendarids") ? JSON.parse($calendarFilter.getAttribute("data-uncheckedcalendarids")) : [];
117
118 // fullCalendar locales are like this: nl-be OR es
119 // The locale we get from WP are en_US OR en.
120 var locale = 'en-us';
121 // This one (data-locale) is set by WP and NOT by the user. User can set it in the fullCalendar config.
122 if ($calendar.getAttribute('data-locale')) {
123 locale = $calendar.getAttribute('data-locale').toLowerCase().replace("_", "-"); // en-us or en
124 }
125
126 // This can be overridden by shortcode attributes.
127 var defaultConfig = {
128 height: "auto",
129 locale: locale,
130 eventLimit: true
131 };
132 var dataConfig = $calendar.getAttribute("data-config") ? JSON.parse($calendar.getAttribute("data-config")) : {};
133
134 // Cast booleans and int (we also get these as strings)
135 castObjectAttributes(dataConfig);
136
137 var config = Object.assign({}, defaultConfig);
138 Object.keys(dataConfig).forEach(function(key) {
139 var value = castAttrValue(dataConfig[key]);
140 config[underscoreToUpper(key)] = value;
141 });
142
143 // New option for firstDay: +0, +1, +2, etc. instead of 0, 1, 2, etc. ==> FullCalendar expects day number (Sunday = 0), so translate it
144 if (("firstDay" in config) && !int_reg.test(config.firstDay)) {
145 config.firstDay = parseInt(moment().add(config.firstDay, 'd').format('d'), 10);
146 }
147
148 locale = config.locale;
149
150 moment.locale(locale);
151
152 // Users can set specific set of calendars
153 // Only in widget set (data-calendarids)
154 var thisCalendarids = $calendar.getAttribute('data-calendarids') ? JSON.parse($calendar.getAttribute('data-calendarids')) : [];
155 // Only in shortcode
156 // TODO: with new release this can be deleted I think.
157 if ("calendarids" in config) {
158 thisCalendarids = config.calendarids.split(",").map(function(item) {
159 return item.replace(" ", "");
160 });
161 }
162
163 if (isPublic && thisCalendarids.length === 0) {
164 console.error("If you set the 'public' property, you have to specify at least 1 calendar ID in the 'calendarids' property.");
165 }
166
167 function makeSureErrorAndLoadingParentExists() {
168 if (!errorAndLoadingParent) {
169 errorAndLoadingParent = $calendar.querySelector(".fc-view-container");
170 }
171 return !!errorAndLoadingParent;
172 }
173
174 function clearError() {
175 if (errorEl.parentNode) {
176 errorEl.parentNode.removeChild(errorEl);
177 }
178 }
179
180 function clearLoading() {
181 if (loadingEl.parentNode) {
182 loadingEl.parentNode.removeChild(loadingEl);
183 }
184 }
185
186 function setError(msg) {
187 clearLoading();
188 if (makeSureErrorAndLoadingParentExists()) {
189 errorEl.innerText = msg;
190 errorAndLoadingParent.appendChild(errorEl);
191 }
192 }
193
194 function setLoading(msg) {
195 clearError();
196 if (makeSureErrorAndLoadingParentExists()) {
197 loadingEl.innerText = msg;
198 errorAndLoadingParent.appendChild(loadingEl);
199 }
200 }
201
202 function handleCalendarFilter(calendars) {
203
204 allCalendars = calendars;
205
206 // Make sure below happens once.
207 if (selectedCalIds !== null) {
208 return;
209 }
210
211 selectedCalIds = Object.keys(calendars); // default all calendars selected
212 if (uncheckedCalendarIds.length) {
213 var tmp = [];
214 selectedCalIds.forEach(function(key) {
215 if (uncheckedCalendarIds.indexOf(key) === -1) {
216 tmp.push(key);
217 }
218 });
219 selectedCalIds = tmp;
220 }
221
222 if (!filter) return;
223
224 var selectBoxes = [];
225 Object.keys(calendars).forEach(function(key, index) {
226 if (thisCalendarids.length && thisCalendarids.indexOf(key) === -1) {
227 return;
228 }
229 selectBoxes.push('<input id="id_' + calendarCounter + '_' + index + '" type="checkbox" ' + (uncheckedCalendarIds.indexOf(key) === -1 ? "checked" : "") + ' value="' + key + '" />'
230 + '<label for="id_' + calendarCounter + '_' + index + '">'
231 + '<span class="epgc-calendar-color" style="background-color:' + (getConfigBackgroundColor(config) || calendars[key].backgroundColor) + '"></span> ' + (calendars[key].summary || key)
232 + '</label>');
233 });
234 $calendarFilter.innerHTML = '<div class="epgc-calendar-filter-wrapper">' + selectBoxes.join("\n") + '</div>';
235 }
236
237 function getFilteredEvents() {
238 var newEvents = [];
239 if (!currentAllEvents || !selectedCalIds) {
240 return newEvents;
241 }
242 currentAllEvents.forEach(function(item) {
243 if (selectedCalIds.indexOf(item.calId) > -1) {
244 newEvents.push(item);
245 }
246 });
247 return newEvents;
248 }
249
250 function setEvents() {
251 var newEvents = getFilteredEvents();
252 var calendarEvents = fullCalendar.getEvents();
253 fullCalendar.batchRendering(function() {
254 calendarEvents.forEach(function(e) {
255 e.remove();
256 });
257 });
258 fullCalendar.batchRendering(function() {
259 newEvents.forEach(function(e) {
260 fullCalendar.addEvent(e);
261 });
262 });
263 }
264
265 if ($calendarFilter) {
266 $calendarFilter.addEventListener("change", function(e) {
267 selectedCalIds = Array.prototype.map.call(calendarWrapper.querySelectorAll(".epgc-calendar-filter-wrapper input[type='checkbox']:checked"), function(item) {
268 return item.value;
269 });
270 setEvents();
271 });
272 }
273
274 var loadingTimer = null;
275
276 // Add things no one can override.
277 config = Object.assign(config, {
278 loading: function(isLoading, view) {
279 if (isLoading) {
280 loadingTimer = setTimeout(function() {
281 setLoading(epgc_object.trans.loading);
282 }, 300);
283 } else {
284 if (loadingTimer) {
285 clearTimeout(loadingTimer);
286 loadingTimer = null;
287 }
288 clearLoading();
289 }
290 },
291 eventRender: function(info) {
292
293 if (showEventPopup) {
294 var texts = ['<span class="epgc-popup-draghandle dashicons dashicons-screenoptions"></span><div class="epgc-popup-row epgc-event-title"><div class="epgc-popup-row-icon"><span></span></div><div class="epgc-popup-row-value">' + info.event.title + '</div></div>'];
295
296 var date = config.timeZone ? moment.tz(info.event.start, config.timeZone).format("L") : info.event.start.toLocaleDateString();
297
298 texts.push('<div class="epgc-popup-row epgc-event-time"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-clock"></span></div><div class="epgc-popup-row-value">' + date + '<br>');
299 if (info.event.allDay) {
300 texts.push(epgc_object.trans.all_day + "</div></div>");
301 } else {
302 if (config.timeZone) {
303 // info.event.end can be null, for example when someone uses the same start and end time!
304 texts.push(moment.tz(info.event.start, config.timeZone).format("LT")
305 + " - "
306 + moment.tz((info.event.end || info.event.start), config.timeZone).format("LT") + "</div></div>");
307 } else {
308 // info.event.end can be null, for example when someone uses the same start and end time!
309 texts.push(info.event.start.toLocaleTimeString(locale, {
310 timeStyle: "short"
311 }) + " - " + (info.event.end || info.event.start).toLocaleTimeString(locale, {
312 timeStyle: "short"
313 }) + "</div></div>");
314 }
315 }
316 if (showEventDescription && info.event.extendedProps.description) {
317 texts.push('<div class="epgc-popup-row epgc-event-description"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-editor-alignleft"></span></div><div class="epgc-popup-row-value">' + info.event.extendedProps.description + '</div></div>');
318 }
319 if (showEventLocation && info.event.extendedProps.location) {
320 texts.push('<div class="epgc-popup-row epgc-event-location"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-location"></span></div><div class="epgc-popup-row-value">' + info.event.extendedProps.location + '</div></div>');
321 }
322 if (showEventAttendees && info.event.extendedProps.attendees && info.event.extendedProps.attendees.length) {
323 texts.push('<div class="epgc-popup-row epgc-event-attendees"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-groups"></span></div><div class="epgc-popup-row-value"><ul>' + info.event.extendedProps.attendees.map(function(attendee) {
324 return '<li>' + attendee.email + '</li>';
325 }).join('') + '</ul></div></div>');
326 }
327 if (showEventAttachments && info.event.extendedProps.attachments && info.event.extendedProps.attachments.length) {
328 texts.push('<div class="epgc-popup-row epgc-event-attachments"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-paperclip"></span></div><div class="epgc-popup-row-value"><ul>' + info.event.extendedProps.attachments.map(function(attachment) {
329 return '<li><a rel="noopener noreferrer" target="_blank" href="' + attachment.fileUrl + '">' + attachment.title + '</a></li>';
330 }).join('<br>') + '</ul></div></div>');
331 }
332 var hasCreator = showEventCreator && info.event.extendedProps.creator && (info.event.extendedProps.creator.email || info.event.extendedProps.creator.displayName);
333 if (showEventCalendarname || hasCreator) {
334 texts.push('<div class="epgc-popup-row epgc-event-calendarname-creator"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-calendar-alt"></span></div><div class="epgc-popup-row-value">');
335 if (showEventCalendarname) {
336 texts.push(allCalendars[info.event.extendedProps.calId].summary || info.event.extendedProps.calId);
337 if (hasCreator) {
338 texts.push('<br>');
339 }
340 }
341 if (hasCreator) {
342 texts.push(epgc_object.trans.created_by + ': ' + (info.event.extendedProps.creator.displayName || info.event.extendedProps.creator.email));
343 }
344 texts.push('</div></div>');
345 }
346 if (showEventLink) {
347 texts.push('<div class="epgc-popup-row epgc-event-link"><div class="epgc-popup-row-icon"><span class="dashicons dashicons-external"></span></div><div class="epgc-popup-row-value"><a rel="noopener noreferrer" target="_blank" href="' + info.event.extendedProps.htmlLink + '">' + epgc_object.trans.go_to_event + '</a></div></div>');
348 }
349 info.el.setAttribute("data-tippy-content", texts.join("\n"));
350 info.el.setAttribute("data-calendarid", info.event.extendedProps.calId);
351 }
352 },
353 events: function(arg, successCcallback, failureCallback) {
354 var start = arg.start;
355 var end = arg.end;
356 //var fStart = dateFormat(start);
357 var fStart = arg.startStr;
358 //var fEnd = dateFormat(end);
359 var fEnd = arg.endStr;
360
361 var xhr = new XMLHttpRequest();
362 var formData = new FormData();
363 formData.append("_ajax_nonce", epgc_object.nonce);
364 formData.append("action", "epgc_ajax_get_calendar");
365 formData.append("start", fStart);
366 formData.append("end", fEnd);
367 if ("timeZone" in arg && arg.timeZone) {
368 formData.append("timeZone", arg.timeZone);
369 }
370 formData.append("thisCalendarids", thisCalendarids.join(","));
371 if (isPublic) {
372 formData.append("isPublic", 1);
373 }
374 xhr.onload = function(eLoad) {
375 try {
376 var response = JSON.parse(this.response);
377 if ("error" in response) {
378 throw response;
379 }
380 var items = [];
381 if ("items" in response) {
382 // Merge calendar backgroundcolor and items
383 var calendars = response.calendars;
384 response.items.forEach(function(item) {
385 // Check if we have this calendar - if we get cached items, but someone unselected
386 // a calendar in the admin, we can get items for unselected calendars.
387 if (!(item.calId in calendars)) return;
388 if (item.bColor) {
389 item.backgroundColor = item.bColor;
390 item.textColor = item.fColor;
391 } else if (!getConfigBackgroundColor(config)) {
392 item.backgroundColor = calendars[item.calId].backgroundColor;
393 }
394 items.push(item);
395 });
396 currentAllEvents = items;
397 handleCalendarFilter(response.calendars);
398 }
399 successCcallback([]);
400 setEvents();
401 } catch (ex) {
402 setError(ex.errorDescription || ex.error || epgc_object.trans.unknown_error);
403 console.error(ex);
404 successCcallback([]);
405 } finally {
406 xhr = null;
407 }
408 };
409 xhr.onerror = function(eError) {
410 setError(eError.error || epgc_object.trans.request_error);
411 console.error(eError);
412 successCcallback([]);
413 };
414 xhr.open("POST", epgc_object.ajax_url);
415 xhr.send(formData);
416 }
417 });
418
419 // Can be true, false, or numeric, even 0 meaning the same as true.
420 if (hidePassed || hideFuture || hidePassed === 0 || hideFuture === 0) {
421 config.validRange = {};
422 }
423
424 if (hidePassed === true || hidePassed === 0) {
425 config.validRange.start = new Date();
426 } else if (hidePassed) {
427 config.validRange.start = moment().subtract(hidePassed, 'days').toDate();
428 }
429 if (hideFuture === true || hideFuture === 0) {
430 config.validRange.end = new Date();
431 } else if (hideFuture) {
432 config.validRange.end = moment().add(hideFuture, 'days').toDate();
433 }
434
435 fullCalendar = new FullCalendar.Calendar($calendar, Object.assign({
436 plugins: ['moment', 'momentTimezone', 'dayGrid', 'list', 'timeGrid'],
437 defaultView: 'dayGridMonth',
438 nowIndicator: true,
439 columnHeader: true,
440 columnHeaderFormat: {
441 weekday: 'short'
442 }
443 }, config));
444 fullCalendar.render();
445 // For debugging, so we have access to it from within the console.
446 window.fullCalendars.push(fullCalendar);
447 }
448
449 window.epgcInitWrappers = function () {
450 var wrappers = document.querySelectorAll(".epgc-calendar-wrapper:not([data-epgc-initialized])");
451 Array.prototype.forEach.call(wrappers, function (w, i) {
452 w.setAttribute("data-epgc-initialized", "1");
453 initWrapper(w, i);
454 });
455 };
456 window.epgcInitWrappers();
457
458 // Auto-init wrappers that arrive after document.ready — Elementor's
459 // editor preview iframe re-renders widgets on attribute change, and
460 // Gutenberg's ServerSideRender mounts HTML asynchronously. A single
461 // observer covers both. Throttled via rAF to coalesce burst mutations.
462 if (typeof MutationObserver !== "undefined") {
463 var scheduled = false;
464 var observer = new MutationObserver(function () {
465 if (scheduled) return;
466 scheduled = true;
467 (window.requestAnimationFrame || setTimeout)(function () {
468 scheduled = false;
469 window.epgcInitWrappers();
470 }, 0);
471 });
472 observer.observe(document.body, { childList: true, subtree: true });
473 }
474
475 var tippyArg = {
476 target: "*[data-tippy-content]",
477 allowHTML: true,
478 theme: "epgc",
479 interactive: true,
480 appendTo: document.body,
481 theme: 'light-border',
482 onMount: function(instance) {
483 Array.prototype.forEach.call(instance.popper.querySelectorAll("a"), function(a) {
484 if (!a.getAttribute("target")) {
485 a.setAttribute("target", "_blank");
486 a.setAttribute("rel", "noopener noreferrer");
487 }
488 });
489 }
490 };
491
492 if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
493 tippyArg.trigger = "click";
494 }
495
496 tippy.delegate("body", tippyArg);
497
498 var startClientX = 0;
499 var startClientY = 0;
500 var popupElement = null;
501 var popupElementStartX = 0;
502 var popupElementStartY = 0;
503
504 function onBodyMouseDown(e) {
505
506 var el = e.target || e.srcElement;
507
508 if (!el.classList.contains('epgc-popup-draghandle')) return;
509
510 while (el) {
511 if (el.getAttribute && el.hasAttribute("data-tippy-root")) {
512 popupElement = el;
513 break;
514 }
515 el = el.parentNode;
516 }
517
518 if (!popupElement) return;
519 var transform = popupElement.style.transform.replace("translate(", "").replace(")", "").split(",");
520 popupElementStartX = parseInt(transform[0].replace(" ", ""), 10);
521 popupElementStartY = parseInt(transform[1].replace(" ", ""), 10);
522 startClientX = e.clientX;
523 startClientY = e.clientY;
524 document.body.addEventListener("mousemove", onBodyMouseMove);
525 document.body.addEventListener("mouseup", onBodyMouseUp);
526 }
527
528 function onBodyMouseMove(e) {
529 popupElement.style.transform = "translate(" + (popupElementStartX + (e.clientX - startClientX)) + "px, " + (popupElementStartY + (e.clientY - startClientY)) + "px)";
530 }
531
532 function onBodyMouseUp() {
533 document.body.removeEventListener("mousemove", onBodyMouseMove);
534 document.body.removeEventListener("mouseup", onBodyMouseUp);
535 }
536
537 document.body.addEventListener("mousedown", onBodyMouseDown);
538 });
539
540 }(this));
541