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