Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('moodle-atto_h5p-button', function (Y, NAME) {
2
 
3
// This file is part of Moodle - http://moodle.org/
4
//
5
// Moodle is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
9
//
10
// Moodle is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
//
15
// You should have received a copy of the GNU General Public License
16
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17
 
18
/*
19
 * @package    atto_h5p
20
 * @copyright  2019 Bas Brands  <bas@moodle.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
/**
25
 * @module moodle-atto_h5p-button
26
 */
27
 
28
/**
29
 * Atto h5p content tool.
30
 *
31
 * @namespace M.atto_h5p
32
 * @class Button
33
 * @extends M.editor_atto.EditorPlugin
34
 */
35
 
36
var CSS = {
37
        CONTENTWARNING: 'att_h5p_contentwarning',
38
        H5PBROWSER: 'openh5pbrowser',
39
        INPUTALT: 'atto_h5p_altentry',
40
        INPUTH5PFILE: 'atto_h5p_file',
41
        INPUTSUBMIT: 'atto_h5p_urlentrysubmit',
42
        OPTION_DOWNLOAD_BUTTON: 'atto_h5p_option_download_button',
43
        OPTION_COPYRIGHT_BUTTON: 'atto_h5p_option_copyright_button',
44
        OPTION_EMBED_BUTTON: 'atto_h5p_option_embed_button',
45
        URLWARNING: 'atto_h5p_warning'
46
    },
47
    SELECTORS = {
48
        CONTENTWARNING: '.' + CSS.CONTENTWARNING,
49
        H5PBROWSER: '.' + CSS.H5PBROWSER,
50
        INPUTH5PFILE: '.' + CSS.INPUTH5PFILE,
51
        INPUTSUBMIT: '.' + CSS.INPUTSUBMIT,
52
        OPTION_DOWNLOAD_BUTTON: '.' + CSS.OPTION_DOWNLOAD_BUTTON,
53
        OPTION_COPYRIGHT_BUTTON: '.' + CSS.OPTION_COPYRIGHT_BUTTON,
54
        OPTION_EMBED_BUTTON: '.' + CSS.OPTION_EMBED_BUTTON,
55
        URLWARNING: '.' + CSS.URLWARNING
56
    },
57
 
58
    COMPONENTNAME = 'atto_h5p',
59
 
60
    TEMPLATE = '' +
61
            '<form class="atto_form mform" id="{{elementid}}_atto_h5p_form">' +
62
                '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.CONTENTWARNING}}">' +
63
                    '{{get_string "noh5pcontent" component}}' +
64
                '</div>' +
65
                '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.URLWARNING}}">' +
66
                    '{{get_string "invalidh5purl" component}}' +
67
                '</div>' +
68
                '{{#if canUploadAndEmbed}}' +
69
                    '<div class="mt-2 mb-4 attoh5pinstructions">{{{get_string "instructions" component}}}</div>' +
70
                '{{/if}}' +
71
                '<div class="mb-4">' +
72
                    '<label for="{{elementid}}_{{CSS.H5PBROWSER}}">' +
73
                        '{{#if canUploadAndEmbed}}' +
74
                            '{{get_string "h5pfileorurl" component}}' +
75
                        '{{/if}}' +
76
                        '{{^if canUploadAndEmbed}}' +
77
                            '{{#if canUpload}}' +
78
                                '{{get_string "h5pfile" component}}' +
79
                            '{{/if}}' +
80
                            '{{#if canEmbed}}' +
81
                                '{{get_string "h5purl" component}}' +
82
                            '{{/if}}' +
83
                        '{{/if}}' +
84
                    '</label>' +
85
                    '<div class="input-group input-append w-100">' +
86
                        '<input class="form-control {{CSS.INPUTH5PFILE}}" type="url" value="{{fileURL}}" ' +
87
                        'id="{{elementid}}_{{CSS.INPUTH5PFILE}}" data-region="h5pfile" size="32"/>' +
88
                        '{{#if canUpload}}' +
89
                            '<span class="input-group-append">' +
90
                                '<button class="btn btn-secondary {{CSS.H5PBROWSER}}" type="button">' +
91
                                '{{get_string "browserepositories" component}}</button>' +
92
                            '</span>' +
93
                        '{{/if}}' +
94
                    '</div>' +
95
                    '{{#if canUpload}}' +
96
                        '<fieldset class="mt-2 collapsible" id="{{elementid}}_h5poptions">' +
97
                            '<legend class="d-flex align-items-center px-1">' +
98
                                '<div class="position-relative d-flex ftoggler align-items-center position-relative mr-1">' +
99
                                    '<a role="button" data-toggle="collapse" href="#h5poptions"' +
100
                                    'aria-expanded="{{#if showOptions}}true{{/if}}{{^if showOptions}}false{{/if}}"' +
101
                                        'aria-controls="h5poptions"' +
102
                                        'class="btn btn-icon mr-1 icons-collapse-expand stretched-link fheader collapsed">' +
103
                                        '<span class="expanded-icon icon-no-margin p-2"' +
104
                                            'title="{{get_string "collapse" "moodle"}}">' +
105
                                            '<i class="icon fa fa-chevron-down fa-fw " aria-hidden="true"></i>' +
106
                                        '</span>' +
107
                                        '<span class="collapsed-icon icon-no-margin p-2"' +
108
                                            'title="{{get_string "expand" "moodle"}}">' +
109
                                            '<span class="dir-rtl-hide">' +
110
                                                '<i class="icon fa fa-chevron-right fa-fw " aria-hidden="true"></i>' +
111
                                            '</span>' +
112
                                            '<span class="dir-ltr-hide">' +
113
                                                '<i class="icon fa fa-chevron-left fa-fw " aria-hidden="true"></i>' +
114
                                            '</span>' +
115
                                        '</span>' +
116
                                        '<span class="sr-only">{{get_string "h5poptions" component}}</span>' +
117
                                    '</a>' +
118
                                    '<h3 class="d-flex align-self-stretch align-items-center mb-0" aria-hidden="true">' +
119
                                        '{{get_string "h5poptions" component}}' +
120
                                    '</h3>' +
121
                                '</div>' +
122
                            '</legend>' +
123
                            '<div id="h5poptions" class="fcontainer collapseable collapse px-1 {{#if showOptions}}show{{/if}}">' +
124
                                '<div class="form-check">' +
125
                                    '<input type="checkbox" {{optionDownloadButton}} ' +
126
                                    'class="form-check-input {{CSS.OPTION_DOWNLOAD_BUTTON}}"' +
127
                                    'aria-label="{{get_string "downloadbutton" component}}" ' +
128
                                    'id="{{elementid}}_h5p-option-allow-download"/>' +
129
                                    '<label class="form-check-label" for="{{elementid}}_h5p-option-allow-download">' +
130
                                    '{{get_string "downloadbutton" component}}' +
131
                                    '</label>' +
132
                                '</div>' +
133
                                '<div class="form-check">' +
134
                                    '<input type="checkbox" {{optionEmbedButton}} ' +
135
                                    'class="form-check-input {{CSS.OPTION_EMBED_BUTTON}}" ' +
136
                                    'aria-label="{{get_string "embedbutton" component}}" ' +
137
                                        'id="{{elementid}}_h5p-option-embed-button"/>' +
138
                                    '<label class="form-check-label" for="{{elementid}}_h5p-option-embed-button">' +
139
                                    '{{get_string "embedbutton" component}}' +
140
                                    '</label>' +
141
                                '</div>' +
142
                                '<div class="form-check mb-2">' +
143
                                    '<input type="checkbox" {{optionCopyrightButton}} ' +
144
                                    'class="form-check-input {{CSS.OPTION_COPYRIGHT_BUTTON}}" ' +
145
                                    'aria-label="{{get_string "copyrightbutton" component}}" ' +
146
                                        'id="{{elementid}}_h5p-option-copyright-button"/>' +
147
                                    '<label class="form-check-label" for="{{elementid}}_h5p-option-copyright-button">' +
148
                                    '{{get_string "copyrightbutton" component}}' +
149
                                    '</label>' +
150
                                '</div>' +
151
                            '</div>' +
152
                        '</fieldset>' +
153
                    '{{/if}}' +
154
                '</div>' +
155
                '<div class="text-center">' +
156
                '<button class="btn btn-secondary {{CSS.INPUTSUBMIT}}" type="submit">' + '' +
157
                    '{{get_string "pluginname" component}}</button>' +
158
                '</div>' +
159
            '</form>',
160
 
161
        H5PTEMPLATE = '' +
162
            '{{#if addParagraphs}}<p><br></p>{{/if}}' +
163
            '<div class="h5p-placeholder" contenteditable="false">' +
164
                '{{{url}}}' +
165
            '</div>' +
166
            '{{#if addParagraphs}}<p><br></p>{{/if}}';
167
 
168
Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
169
    /**
170
     * A reference to the current selection at the time that the dialogue
171
     * was opened.
172
     *
173
     * @property _currentSelection
174
     * @type Range
175
     * @private
176
     */
177
    _currentSelection: null,
178
 
179
    /**
180
     * A reference to the currently open form.
181
     *
182
     * @param _form
183
     * @type Node
184
     * @private
185
     */
186
    _form: null,
187
 
188
    /**
189
     * A reference to the currently selected H5P div.
190
     *
191
     * @param _form
192
     * @type Node
193
     * @private
194
     */
195
    _H5PDiv: null,
196
 
197
    /**
198
     * Allowed methods of adding H5P.
199
     *
200
     * @param _allowedmethods
201
     * @type String
202
     * @private
203
     */
204
    _allowedmethods: 'none',
205
 
206
    initializer: function() {
207
        this._allowedmethods = this.get('allowedmethods');
208
        if (this._allowedmethods === 'none') {
209
            // Plugin not available here.
210
            return;
211
        }
212
        this.addButton({
213
            icon: 'icon',
214
            iconComponent: 'atto_h5p',
215
            callback: this._displayDialogue,
216
            tags: '.h5p-placeholder',
217
            tagMatchRequiresAll: false
218
        });
219
 
220
        this.editor.all('.h5p-placeholder').setAttribute('contenteditable', 'false');
221
        this.editor.delegate('dblclick', this._handleDblClick, '.h5p-placeholder', this);
222
        this.editor.delegate('click', this._handleClick, '.h5p-placeholder', this);
223
    },
224
 
225
    /**
226
     * Handle a double click on a H5P Placeholder.
227
     *
228
     * @method _handleDblClick
229
     * @private
230
     */
231
    _handleDblClick: function() {
232
        this._displayDialogue();
233
    },
234
 
235
    /**
236
     * Handle a click on a H5P Placeholder.
237
     *
238
     * @method _handleClick
239
     * @param {EventFacade} e
240
     * @private
241
     */
242
    _handleClick: function(e) {
243
        var selection = this.get('host').getSelectionFromNode(e.target);
244
        if (this.get('host').getSelection() !== selection) {
245
            this.get('host').setSelection(selection);
246
        }
247
    },
248
 
249
    /**
250
     * Display the h5p editing tool.
251
     *
252
     * @method _displayDialogue
253
     * @private
254
     */
255
    _displayDialogue: function() {
256
        // Store the current selection.
257
        this._currentSelection = this.get('host').getSelection();
258
 
259
        if (this._currentSelection === false) {
260
            return;
261
        }
262
 
263
        this._getH5PDiv();
264
 
265
        var dialogue = this.getDialogue({
266
            headerContent: M.util.get_string('pluginname', COMPONENTNAME),
267
            width: 'auto',
268
            focusAfterHide: true
269
        });
270
        // Set the dialogue content, and then show the dialogue.
271
        dialogue.set('bodyContent', this._getDialogueContent())
272
            .show();
273
        M.form.shortforms({formid: this.get('host').get('elementid') + '_atto_h5p_form'});
274
    },
275
 
276
    /**
277
     * Get the H5P iframe
278
     *
279
     * @method _resolveH5P
280
     * @return {Node} The H5P iframe selected.
281
     * @private
282
     */
283
    _getH5PDiv: function() {
284
        var selectednodes = this.get('host').getSelectedNodes();
285
        var H5PDiv = null;
286
        selectednodes.each(function(selNode) {
287
            if (selNode.hasClass('h5p-placeholder')) {
288
                H5PDiv = selNode;
289
            }
290
        });
291
        this._H5PDiv = H5PDiv;
292
    },
293
 
294
    /**
295
     * Get the H5P button permissions.
296
     *
297
     * @return {Object} H5P button permissions.
298
     * @private
299
     */
300
    _getPermissions: function() {
301
        var permissions = {
302
            'canEmbed': false,
303
            'canUpload': false,
304
            'canUploadAndEmbed': false
305
        };
306
 
307
        if (this.get('host').canShowFilepicker('h5p')) {
308
            if (this._allowedmethods === 'both') {
309
                permissions.canUploadAndEmbed = true;
310
                permissions.canUpload = true;
311
            } else if (this._allowedmethods === 'upload') {
312
                permissions.canUpload = true;
313
            }
314
        }
315
 
316
        if (this._allowedmethods === 'both' || this._allowedmethods === 'embed') {
317
            permissions.canEmbed = true;
318
        }
319
        return permissions;
320
    },
321
 
322
 
323
    /**
324
     * Return the dialogue content for the tool, attaching any required
325
     * events.
326
     *
327
     * @method _getDialogueContent
328
     * @return {Node} The content to place in the dialogue.
329
     * @private
330
     */
331
    _getDialogueContent: function() {
332
 
333
        var permissions = this._getPermissions();
334
 
335
        var fileURL,
336
            optionDownloadButton,
337
            optionEmbedButton,
338
            optionCopyrightButton,
339
            showOptions = false;
340
 
341
        if (this._H5PDiv) {
342
            var H5PURL = this._H5PDiv.get('innerHTML');
343
            var fileBaseUrl = M.cfg.wwwroot + '/draftfile.php';
344
            if (fileBaseUrl == H5PURL.substring(0, fileBaseUrl.length)) {
345
                fileURL = H5PURL.split("?")[0];
346
 
347
                var parameters = H5PURL.split("?")[1];
348
                if (parameters) {
349
                    if (parameters.match(/export=1/)) {
350
                        optionDownloadButton = 'checked';
351
                        showOptions = true;
352
                    }
353
 
354
                    if (parameters.match(/embed=1/)) {
355
                        optionEmbedButton = 'checked';
356
                        showOptions = true;
357
                    }
358
 
359
                    if (parameters.match(/copyright=1/)) {
360
                        optionCopyrightButton = 'checked';
361
                        showOptions = true;
362
                    }
363
                }
364
            } else {
365
                fileURL = H5PURL;
366
            }
367
        }
368
 
369
        var template = Y.Handlebars.compile(TEMPLATE),
370
            content = Y.Node.create(template({
371
                elementid: this.get('host').get('elementid'),
372
                CSS: CSS,
373
                component: COMPONENTNAME,
374
                canUpload: permissions.canUpload,
375
                canEmbed: permissions.canEmbed,
376
                canUploadAndEmbed: permissions.canUploadAndEmbed,
377
                showOptions: showOptions,
378
                fileURL: fileURL,
379
                optionDownloadButton: optionDownloadButton,
380
                optionEmbedButton: optionEmbedButton,
381
                optionCopyrightButton: optionCopyrightButton
382
            }));
383
 
384
        this._form = content;
385
 
386
        // Listen to and act on Dialogue content events.
387
        this._setEventListeners();
388
 
389
        return content;
390
    },
391
 
392
    /**
393
     * Update the dialogue after an h5p was selected in the File Picker.
394
     *
395
     * @method _filepickerCallback
396
     * @param {object} params The parameters provided by the filepicker
397
     * containing information about the h5p.
398
     * @private
399
     */
400
    _filepickerCallback: function(params) {
401
        if (params.url !== '') {
402
            var input = this._form.one(SELECTORS.INPUTH5PFILE);
403
            input.set('value', params.url);
404
            this._removeWarnings();
405
        }
406
    },
407
 
408
    /**
409
     * Set event Listeners for Dialogue content actions.
410
     *
411
     * @method  _setEventListeners
412
     * @private
413
     */
414
    _setEventListeners: function() {
415
        var form = this._form;
416
        var permissions = this._getPermissions();
417
 
418
        form.one(SELECTORS.INPUTSUBMIT).on('click', this._setH5P, this);
419
 
420
        if (permissions.canUpload) {
421
            form.one(SELECTORS.H5PBROWSER).on('click', function() {
422
                this.get('host').showFilepicker('h5p', this._filepickerCallback, this);
423
            }, this);
424
        }
425
 
426
        if (permissions.canUploadAndEmbed) {
427
            form.one(SELECTORS.INPUTH5PFILE).on('change', function() {
428
                this._removeWarnings();
429
            }, this);
430
        }
431
    },
432
 
433
    /**
434
     * Remove warnings shown in the dialogue.
435
     *
436
     * @method _removeWarnings
437
     * @private
438
     */
439
    _removeWarnings: function() {
440
        var form = this._form;
441
        form.one(SELECTORS.URLWARNING).setStyle('display', 'none');
442
        form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'none');
443
    },
444
 
445
    /**
446
     * Update the h5p in the contenteditable.
447
     *
448
     * @method _setH5P
449
     * @param {EventFacade} e
450
     * @private
451
     */
452
    _setH5P: function(e) {
453
        var form = this._form,
454
            h5phtml,
455
            host = this.get('host'),
456
            h5pfile = form.one(SELECTORS.INPUTH5PFILE).get('value'),
457
            permissions = this._getPermissions();
458
 
459
        e.preventDefault();
460
 
461
        // Check if there are any issues.
462
        if (this._updateWarning()) {
463
            return;
464
        }
465
 
466
        // Focus on the editor in preparation for inserting the H5P.
467
        host.focus();
468
 
469
        // Add an empty paragraph after new H5P container that can catch the cursor.
470
        var addParagraphs = true;
471
 
472
        // If a H5P placeholder was selected we can destroy it now.
473
        if (this._H5PDiv) {
474
            this._H5PDiv.remove();
475
            addParagraphs = false;
476
        }
477
 
478
        if (h5pfile !== '') {
479
            host.setSelection(this._currentSelection);
480
 
481
            if (h5pfile.startsWith(M.cfg.wwwroot)) {
482
                // It's a local file.
483
                var params = '';
484
                if (permissions.canUpload) {
485
                    var options = {};
486
                    if (form.one(SELECTORS.OPTION_DOWNLOAD_BUTTON).get('checked')) {
487
                        options['export'] = '1';
488
                    }
489
                    if (form.one(SELECTORS.OPTION_EMBED_BUTTON).get('checked')) {
490
                        options.embed = '1';
491
                    }
492
                    if (form.one(SELECTORS.OPTION_COPYRIGHT_BUTTON).get('checked')) {
493
                        options.copyright = '1';
494
                    }
495
 
496
                    for (var opt in options) {
497
                        if (params === "" && (h5pfile.indexOf("?") === -1)) {
498
                            params += "?";
499
                        } else {
500
                            params += "&amp;";
501
                        }
502
                        params += opt + "=" + options[opt];
503
                    }
504
                }
505
 
506
                var h5ptemplate = Y.Handlebars.compile(H5PTEMPLATE);
507
 
508
                h5phtml = h5ptemplate({
509
                    url: h5pfile + params,
510
                    addParagraphs: addParagraphs
511
                });
512
            } else {
513
                // It's a URL.
514
                var urltemplate = Y.Handlebars.compile(H5PTEMPLATE);
515
                h5phtml = urltemplate({
516
                    url: h5pfile
517
                });
518
            }
519
 
520
            host.insertContentAtFocusPoint(h5phtml);
521
 
522
            this.markUpdated();
523
        }
524
 
525
        this.getDialogue({
526
            focusAfterHide: null
527
        }).hide();
528
    },
529
 
530
    /**
531
     * Check if this could be a h5p embed.
532
     *
533
     * @method _validEmbed
534
     * @param {String} str
535
     * @return {boolean} whether this is a iframe tag.
536
     * @private
537
     */
538
    _validEmbed: function(str) {
539
        var pattern = new RegExp('^(<iframe).*(<\\/iframe>)'); // Port and path.
540
        return !!pattern.test(str);
541
    },
542
 
543
    /**
544
     * Check if this could be a h5p URL.
545
     *
546
     * @method _validURL
547
     * @param {String} str
548
     * @return {boolean} whether this is a valid URL.
549
     * @private
550
     */
551
    _validURL: function(str) {
552
        var pattern = new RegExp('^(https?:\\/\\/)?' + // Protocol.
553
            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // Domain name.
554
            '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address.
555
            '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'); // Port and path.
556
        return !!pattern.test(str);
557
    },
558
 
559
    /**
560
     * Update the url warning.
561
     *
562
     * @method _updateWarning
563
     * @return {boolean} whether a warning should be displayed.
564
     * @private
565
     */
566
    _updateWarning: function() {
567
        var form = this._form,
568
            state = true,
569
            h5pfile,
570
            permissions = this._getPermissions();
571
 
572
        if (permissions.canUpload || permissions.canEmbed) {
573
            h5pfile = form.one(SELECTORS.INPUTH5PFILE).get('value');
574
            if (h5pfile !== '') {
575
                form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'none');
576
                if (h5pfile.startsWith(M.cfg.wwwroot) || this._validURL(h5pfile)) {
577
                    // Only external URLs have to be validated.
578
                    form.one(SELECTORS.URLWARNING).setStyle('display', 'none');
579
                    state = false;
580
                } else {
581
                    form.one(SELECTORS.URLWARNING).setStyle('display', 'block');
582
                    state = true;
583
                }
584
            } else {
585
                form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'block');
586
                state = true;
587
            }
588
        }
589
 
590
        return state;
591
    }
592
}, {
593
    ATTRS: {
594
        /**
595
         * The allowedmethods of adding h5p content.
596
         *
597
         * @attribute allowedmethods
598
         * @type String
599
         */
600
        allowedmethods: {
601
            value: null
602
        }
603
    }
604
});
605
 
606
 
607
}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});