1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
|
var onYouTubeIframeAPIReady = undefined;
odoo.define('website_slides.fullscreen', function (require) {
'use strict';
var publicWidget = require('web.public.widget');
var core = require('web.core');
var config = require('web.config');
var QWeb = core.qweb;
var _t = core._t;
var session = require('web.session');
var Quiz = require('website_slides.quiz').Quiz;
var Dialog = require('web.Dialog');
require('website_slides.course.join.widget');
/**
* Helper: Get the slide dict matching the given criteria
*
* @private
* @param {Array<Object>} slideList List of dict reprensenting a slide
* @param {Object} matcher (see https://underscorejs.org/#matcher)
*/
var findSlide = function (slideList, matcher) {
var slideMatch = _.matcher(matcher);
return _.find(slideList, slideMatch);
};
/**
* This widget is responsible of display Youtube Player
*
* The widget will trigger an event `change_slide` when the video is at
* its end, and `slide_completed` when the player is at 30 sec before the
* end of the video (30 sec before is considered as completed).
*/
var VideoPlayer = publicWidget.Widget.extend({
template: 'website.slides.fullscreen.video',
youtubeUrl: 'https://www.youtube.com/iframe_api',
init: function (parent, slide) {
this.slide = slide;
return this._super.apply(this, arguments);
},
start: function (){
var self = this;
return Promise.all([this._super.apply(this, arguments), this._loadYoutubeAPI()]).then(function() {
self._setupYoutubePlayer();
});
},
_loadYoutubeAPI: function () {
var self = this;
var prom = new Promise(function (resolve, reject) {
if ($(document).find('script[src="' + self.youtubeUrl + '"]').length === 0) {
var $youtubeElement = $('<script/>', {src: self.youtubeUrl});
$(document.head).append($youtubeElement);
// function called when the Youtube asset is loaded
// see https://developers.google.com/youtube/iframe_api_reference#Requirements
onYouTubeIframeAPIReady = function () {
resolve();
};
} else {
resolve();
}
});
return prom;
},
/**
* Links the youtube api to the iframe present in the template
*
* @private
*/
_setupYoutubePlayer: function (){
this.player = new YT.Player('youtube-player' + this.slide.id, {
playerVars: {
'autoplay': 1,
'origin': window.location.origin
},
events: {
'onStateChange': this._onPlayerStateChange.bind(this)
}
});
},
/**
* Specific method of the youtube api.
* Whenever the player starts playing/pausing/buffering/..., a setinterval is created.
* This setinterval is used to check te user's progress in the video.
* Once the user reaches a particular time in the video (30s before end), the slide will be considered as completed
* if the video doesn't have a mini-quiz.
* This method also allows to automatically go to the next slide (or the quiz associated to the current
* video) once the video is over
*
* @private
* @param {*} event
*/
_onPlayerStateChange: function (event){
var self = this;
if (self.slide.completed) {
return;
}
if (event.data !== YT.PlayerState.ENDED) {
if (!event.target.getCurrentTime) {
return;
}
if (self.tid) {
clearInterval(self.tid);
}
self.currentVideoTime = event.target.getCurrentTime();
self.totalVideoTime = event.target.getDuration();
self.tid = setInterval(function (){
self.currentVideoTime += 1;
if (self.totalVideoTime && self.currentVideoTime > self.totalVideoTime - 30){
clearInterval(self.tid);
if (!self.slide.hasQuestion && !self.slide.completed){
self.trigger_up('slide_to_complete', self.slide);
}
}
}, 1000);
} else {
if (self.tid) {
clearInterval(self.tid);
}
this.player = undefined;
if (this.slide.hasNext) {
this.trigger_up('slide_go_next');
}
}
},
});
/**
* This widget is responsible of navigation for one slide to another:
* - by clicking on any slide list entry
* - by mouse click (next / prev)
* - by recieving the order to go to prev/next slide (`goPrevious` and `goNext` public methods)
*
* The widget will trigger an event `change_slide` with
* the `slideId` and `isMiniQuiz` as data.
*/
var Sidebar = publicWidget.Widget.extend({
events: {
"click .o_wslides_fs_sidebar_list_item": '_onClickTab',
},
init: function (parent, slideList, defaultSlide) {
var result = this._super.apply(this, arguments);
this.slideEntries = slideList;
this.set('slideEntry', defaultSlide);
return result;
},
start: function (){
var self = this;
this.on('change:slideEntry', this, this._onChangeCurrentSlide);
return this._super.apply(this, arguments).then(function (){
$(document).keydown(self._onKeyDown.bind(self));
});
},
destroy: function () {
$(document).unbind('keydown', this._onKeyDown.bind(this));
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Change the current slide with the next one (if there is one).
*
* @public
*/
goNext: function () {
var currentIndex = this._getCurrentIndex();
if (currentIndex < this.slideEntries.length-1) {
this.set('slideEntry', this.slideEntries[currentIndex+1]);
}
},
/**
* Change the current slide with the previous one (if there is one).
*
* @public
*/
goPrevious: function () {
var currentIndex = this._getCurrentIndex();
if (currentIndex >= 1) {
this.set('slideEntry', this.slideEntries[currentIndex-1]);
}
},
/**
* Greens up the bullet when the slide is completed
*
* @public
* @param {Integer} slideId
*/
setSlideCompleted: function (slideId) {
var $elem = this.$('.fa-circle-thin[data-slide-id="'+slideId+'"]');
$elem.removeClass('fa-circle-thin').addClass('fa-check text-success o_wslides_slide_completed');
},
/**
* Updates the progressbar whenever a lesson is completed
*
* @public
* @param {*} channelCompletion
*/
updateProgressbar: function (channelCompletion) {
var completion = Math.min(100, channelCompletion);
this.$('.progress-bar').css('width', completion + "%" );
this.$('.o_wslides_progress_percentage').text(completion);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Get the index of the current slide entry (slide and/or quiz)
*/
_getCurrentIndex: function () {
var slide = this.get('slideEntry');
var currentIndex = _.findIndex(this.slideEntries, function (entry) {
return entry.id === slide.id && entry.isQuiz === slide.isQuiz;
});
return currentIndex;
},
//--------------------------------------------------------------------------
// Handler
//--------------------------------------------------------------------------
/**
* Handler called whenever the user clicks on a sub-quiz which is linked to a slide.
* This does NOT handle the case of a slide of type "quiz".
* By going through this handler, the widget will be able to determine that it has to render
* the associated quiz and not the main content.
*
* @private
* @param {*} ev
*/
_onClickMiniQuiz: function (ev){
var slideID = parseInt($(ev.currentTarget).data().slide_id);
this.set('slideEntry',{
slideID: slideID,
isMiniQuiz: true
});
this.trigger_up('change_slide', this.get('slideEntry'));
},
/**
* Handler called when the user clicks on a normal slide tab
*
* @private
* @param {*} ev
*/
_onClickTab: function (ev) {
ev.stopPropagation();
var $elem = $(ev.currentTarget);
if ($elem.data('canAccess') === 'True') {
var isQuiz = $elem.data('isQuiz');
var slideID = parseInt($elem.data('id'));
var slide = findSlide(this.slideEntries, {id: slideID, isQuiz: isQuiz});
this.set('slideEntry', slide);
}
},
/**
* Actively changes the active tab in the sidebar so that it corresponds
* the slide currently displayed
*
* @private
*/
_onChangeCurrentSlide: function () {
var slide = this.get('slideEntry');
this.$('.o_wslides_fs_sidebar_list_item.active').removeClass('active');
var selector = '.o_wslides_fs_sidebar_list_item[data-id='+slide.id+'][data-is-quiz!="1"]';
this.$(selector).addClass('active');
this.trigger_up('change_slide', this.get('slideEntry'));
},
/**
* Binds left and right arrow to allow the user to navigate between slides
*
* @param {*} ev
* @private
*/
_onKeyDown: function (ev){
switch (ev.key){
case "ArrowLeft":
this.goPrevious();
break;
case "ArrowRight":
this.goNext();
break;
}
},
});
var ShareDialog = Dialog.extend({
template: 'website.slide.share.modal',
events: {
'click .o_wslides_js_share_email button': '_onShareByEmailClick',
'click a.o_wslides_js_social_share': '_onSlidesSocialShare',
'click .o_clipboard_button': '_onShareLinkCopy',
},
init: function (parent, options, slide) {
options = _.defaults(options || {}, {
title: "Share",
buttons: [{text: "Cancel", close: true}],
size: 'medium',
});
this._super(parent, options);
this.slide = slide;
this.session = session;
},
_onShareByEmailClick: function() {
var form = this.$('.o_wslides_js_share_email');
var input = form.find('input');
var slideID = form.find('button').data('slide-id');
if (input.val() && input[0].checkValidity()) {
form.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
this._rpc({
route: '/slides/slide/send_share_email',
params: {
slide_id: slideID,
email: input.val(),
fullscreen: true
},
}).then(function () {
form.html('<div class="alert alert-info" role="alert">' + _t('<strong>Thank you!</strong> Mail has been sent.') + '</div>');
});
} else {
form.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
input.focus();
}
},
_onSlidesSocialShare: function (ev) {
ev.preventDefault();
var popUpURL = $(ev.currentTarget).attr('href');
window.open(popUpURL, 'Share Dialog', 'width=626,height=436');
},
_onShareLinkCopy: function (ev) {
ev.preventDefault();
var $clipboardBtn = this.$('.o_clipboard_button');
$clipboardBtn.tooltip({title: "Copied !", trigger: "manual", placement: "bottom"});
var self = this;
var clipboard = new ClipboardJS('.o_clipboard_button', {
target: function () {
return self.$('.o_wslides_js_share_link')[0];
},
container: this.el
});
clipboard.on('success', function () {
clipboard.destroy();
$clipboardBtn.tooltip('show');
_.delay(function () {
$clipboardBtn.tooltip("hide");
}, 800);
});
clipboard.on('error', function (e) {
clipboard.destroy();
})
},
});
var ShareButton = publicWidget.Widget.extend({
events: {
"click .o_wslides_fs_share": '_onClickShareSlide'
},
init: function (el, slide) {
var result = this._super.apply(this, arguments);
this.slide = slide;
return result;
},
_openDialog: function() {
return new ShareDialog(this, {}, this.slide).open();
},
_onClickShareSlide: function (ev) {
ev.preventDefault();
this._openDialog();
},
_onChangeSlide: function (currentSlide) {
this.slide = currentSlide;
}
});
/**
* This widget's purpose is to show content of a course, naviguating through contents
* and correclty display it. It also handle slide completion, course progress, ...
*
* This widget is rendered sever side, and attached to the existing DOM.
*/
var Fullscreen = publicWidget.Widget.extend({
events: {
"click .o_wslides_fs_toggle_sidebar": '_onClickToggleSidebar',
},
custom_events: {
'change_slide': '_onChangeSlideRequest',
'slide_to_complete': '_onSlideToComplete',
'slide_completed': '_onSlideCompleted',
'slide_go_next': '_onSlideGoToNext',
},
/**
* @override
* @param {Object} el
* @param {Object} slides Contains the list of all slides of the course
* @param {integer} defaultSlideId Contains the ID of the slide requested by the user
*/
init: function (parent, slides, defaultSlideId, channelData){
var result = this._super.apply(this,arguments);
this.initialSlideID = defaultSlideId;
this.slides = this._preprocessSlideData(slides);
this.channel = channelData;
var slide;
var urlParams = $.deparam.querystring();
if (defaultSlideId) {
slide = findSlide(this.slides, {id: defaultSlideId, isQuiz: urlParams.quiz === "1" });
} else {
slide = this.slides[0];
}
this.set('slide', slide);
this.sidebar = new Sidebar(this, this.slides, slide);
this.shareButton = new ShareButton(this, slide);
return result;
},
/**
* @override
*/
start: function (){
var self = this;
this.on('change:slide', this, this._onChangeSlide);
this._toggleSidebar();
return this._super.apply(this, arguments).then(function () {
return self._onChangeSlide(); // trigger manually once DOM ready, since slide content is not rendered server side
});
},
/**
* Extended to attach sub widget to sub DOM. This might be experimental but
* seems working fine.
*
* @override
*/
attachTo: function (){
var defs = [this._super.apply(this, arguments)];
defs.push(this.sidebar.attachTo(this.$('.o_wslides_fs_sidebar')));
defs.push(this.shareButton.attachTo(this.$('.o_wslides_slide_fs_header')));
return $.when.apply($, defs);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Fetches content with an rpc call for slides of type "webpage"
*
* @private
*/
_fetchHtmlContent: function (){
var self = this;
var currentSlide = this.get('slide');
return self._rpc({
route:"/slides/slide/get_html_content",
params: {
'slide_id': currentSlide.id
}
}).then(function (data){
if (data.html_content) {
currentSlide.htmlContent = data.html_content;
}
});
},
/**
* Fetches slide content depending on its type.
* If the slide doesn't need to fetch any content, return a resolved deferred
*
* @private
*/
_fetchSlideContent: function (){
var slide = this.get('slide');
if (slide.type === 'webpage' && !slide.isQuiz) {
return this._fetchHtmlContent();
}
return Promise.resolve();
},
_markAsCompleted: function (slideId, completion) {
var slide = findSlide(this.slides, {id: slideId});
slide.completed = true;
this.sidebar.setSlideCompleted(slide.id);
this.sidebar.updateProgressbar(completion);
},
/**
* Extend the slide data list to add informations about rendering method, and other
* specific values according to their slide_type.
*/
_preprocessSlideData: function (slidesDataList) {
slidesDataList.forEach(function (slideData, index) {
// compute hasNext slide
slideData.hasNext = index < slidesDataList.length-1;
// compute embed url
if (slideData.type === 'video') {
slideData.embedCode = $(slideData.embedCode).attr('src') || ""; // embedCode contains an iframe tag, where src attribute is the url (youtube or embed document from odoo)
var separator = slideData.embedCode.indexOf("?") !== -1 ? "&" : "?";
var scheme = slideData.embedCode.indexOf('//') === 0 ? 'https:' : '';
var params = { rel: 0, enablejsapi: 1, origin: window.location.origin };
if (slideData.embedCode.indexOf("//drive.google.com") === -1) {
params.autoplay = 1;
}
slideData.embedUrl = slideData.embedCode ? scheme + slideData.embedCode + separator + $.param(params) : "";
} else if (slideData.type === 'infographic') {
slideData.embedUrl = _.str.sprintf('/web/image/slide.slide/%s/image_1024', slideData.id);
} else if (_.contains(['document', 'presentation'], slideData.type)) {
slideData.embedUrl = $(slideData.embedCode).attr('src');
}
// fill empty property to allow searching on it with _.filter(list, matcher)
slideData.isQuiz = !!slideData.isQuiz;
slideData.hasQuestion = !!slideData.hasQuestion;
// technical settings for the Fullscreen to work
slideData._autoSetDone = _.contains(['infographic', 'presentation', 'document', 'webpage'], slideData.type) && !slideData.hasQuestion;
});
return slidesDataList;
},
/**
* Changes the url whenever the user changes slides.
* This allows the user to refresh the page and stay on the right slide
*
* @private
*/
_pushUrlState: function (){
var urlParts = window.location.pathname.split('/');
urlParts[urlParts.length-1] = this.get('slide').slug;
var url = urlParts.join('/');
this.$('.o_wslides_fs_exit_fullscreen').attr('href', url);
var params = {'fullscreen': 1 };
if (this.get('slide').isQuiz){
params.quiz = 1;
}
var fullscreenUrl = _.str.sprintf('%s?%s', url, $.param(params));
history.pushState(null, '', fullscreenUrl);
},
/**
* Render the current slide content using specific mecanism according to slide type:
* - simply append content (for webpage)
* - template rendering (for image, document, ....)
* - using a sub widget (quiz and video)
*
* @private
* @returns Deferred
*/
_renderSlide: function () {
var slide = this.get('slide');
var $content = this.$('.o_wslides_fs_content');
$content.empty();
// display quiz slide, or quiz attached to a slide
if (slide.type === 'quiz' || slide.isQuiz) {
$content.addClass('bg-white');
var QuizWidget = new Quiz(this, slide, this.channel);
return QuizWidget.appendTo($content);
}
// render slide content
if (_.contains(['document', 'presentation', 'infographic'], slide.type)) {
$content.html(QWeb.render('website.slides.fullscreen.content', {widget: this}));
} else if (slide.type === 'video') {
this.videoPlayer = new VideoPlayer(this, slide);
return this.videoPlayer.appendTo($content);
} else if (slide.type === 'webpage'){
var $wpContainer = $('<div>').addClass('o_wslide_fs_webpage_content bg-white block w-100 overflow-auto');
$(slide.htmlContent).appendTo($wpContainer);
$content.append($wpContainer);
this.trigger_up('widgets_start_request', {
$target: $content,
});
}
return Promise.resolve();
},
/**
* Once the completion conditions are filled,
* rpc call to set the the relation between the slide and the user as "completed"
*
* @private
* @param {Integer} slideId: the id of slide to set as completed
*/
_setCompleted: function (slideId){
var self = this;
var slide = findSlide(this.slides, {id: slideId});
if (!slide.completed) { // no useless RPC call
return this._rpc({
route: '/slides/slide/set_completed',
params: {
slide_id: slide.id,
}
}).then(function (data){
self._markAsCompleted(slideId, data.channel_completion);
return Promise.resolve();
});
}
return Promise.resolve();
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Triggered whenever the user changes slides.
* When the current slide is changed, widget will be automatically updated
* and allowed to: fetch the content if needed, render it, update the url,
* and set slide as "completed" according to its type requirements. In
* mobile case (i.e. limited screensize), sidebar will be toggled since
* sidebar will block most or all of new slide visibility.
*
* @private
*/
_onChangeSlide: function () {
var self = this;
var slide = this.get('slide');
self._pushUrlState();
return this._fetchSlideContent().then(function() { // render content
var websiteName = document.title.split(" | ")[1]; // get the website name from title
document.title = (websiteName) ? slide.name + ' | ' + websiteName : slide.name;
if (config.device.size_class < config.device.SIZES.MD) {
self._toggleSidebar(); // hide sidebar when small device screen
}
return self._renderSlide();
}).then(function() {
if (slide._autoSetDone && !session.is_website_user) { // no useless RPC call
if (['document', 'presentation'].includes(slide.type)) {
// only set the slide as completed after iFrame is loaded to avoid concurrent execution with 'embedUrl' controller
self.el.querySelector('iframe.o_wslides_iframe_viewer').addEventListener('load', () => self._setCompleted(slide.id));
} else {
return self._setCompleted(slide.id);
}
}
});
},
/**
* Changes current slide when receiving custom event `change_slide` with
* its id and if it's its quizz or not we need to display.
*
* @private
*/
_onChangeSlideRequest: function (ev){
var slideData = ev.data;
var newSlide = findSlide(this.slides, {
id: slideData.id,
isQuiz: slideData.isQuiz || false,
});
this.set('slide', newSlide);
this.shareButton._onChangeSlide(newSlide);
},
/**
* Triggered when subwidget has mark the slide as done, and the UI need to be adapted.
*
* @private
*/
_onSlideCompleted: function (ev) {
var slide = ev.data.slide;
var completion = ev.data.completion;
this._markAsCompleted(slide.id, completion);
},
/**
* Triggered when sub widget business is done and that slide
* can now be marked as done.
*
* @private
*/
_onSlideToComplete: function (ev) {
if (!session.is_website_user) { // no useless RPC call
var slideId = ev.data.id;
this._setCompleted(slideId);
}
},
/**
* Go the next slide
*
* @private
*/
_onSlideGoToNext: function (ev) {
this.sidebar.goNext();
},
/**
* Called when the sidebar toggle is clicked -> toggles the sidebar visibility.
*
* @private
*/
_onClickToggleSidebar: function (ev){
ev.preventDefault();
this._toggleSidebar();
},
/**
* Toggles sidebar visibility.
*
* @private
*/
_toggleSidebar: function () {
this.$('.o_wslides_fs_sidebar').toggleClass('o_wslides_fs_sidebar_hidden');
this.$('.o_wslides_fs_toggle_sidebar').toggleClass('active');
},
});
publicWidget.registry.websiteSlidesFullscreenPlayer = publicWidget.Widget.extend({
selector: '.o_wslides_fs_main',
xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml', '/website_slides/static/src/xml/website_slides_share.xml'],
start: function (){
var self = this;
var proms = [this._super.apply(this, arguments)];
var fullscreen = new Fullscreen(this, this._getSlides(), this._getCurrentSlideID(), this._extractChannelData());
proms.push(fullscreen.attachTo(".o_wslides_fs_main"));
return Promise.all(proms).then(function () {
$('#edit-page-menu a[data-action="edit"]').on('click', self._onWebEditorClick.bind(self));
});
},
/**
* The web editor does not work well with the e-learning fullscreen view.
* It actually completely closes the fullscreen view and opens the edition on a blank page.
*
* To avoid this, we intercept the click on the 'edit' button and redirect to the
* non-fullscreen view of this slide with the editor enabled, which is more suited to edit
* in-place anyway.
*
* @param {MouseEvent} e
*/
_onWebEditorClick: function (e) {
e.preventDefault();
e.stopPropagation();
window.location = `${window.location.pathname}?fullscreen=0&enable_editor=1`;
},
_extractChannelData: function (){
return this.$el.data();
},
_getCurrentSlideID: function (){
return parseInt(this.$('.o_wslides_fs_sidebar_list_item.active').data('id'));
},
/**
* @private
* Creates slides objects from every slide-list-cells attributes
*/
_getSlides: function (){
var $slides = this.$('.o_wslides_fs_sidebar_list_item[data-can-access="True"]');
var slideList = [];
$slides.each(function () {
var slideData = $(this).data();
slideList.push(slideData);
});
return slideList;
},
});
return Fullscreen;
});
|