@oat-sa/tao-core-ui 3.22.0 → 3.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/figure/FigureStateActive.js +64 -32
  2. package/dist/image/ImgStateActive/initHelper.js +64 -32
  3. package/dist/image/ImgStateActive.js +64 -32
  4. package/dist/mediaplayer/players/html5.js +44 -18
  5. package/dist/mediaplayer/utils/crossOrigin.js +64 -0
  6. package/dist/mediaplayer.js +7 -1
  7. package/dist/previewer.js +1 -1
  8. package/dist/resourcemgr/assetSearch.js +793 -0
  9. package/dist/resourcemgr/assetSearchContract.js +477 -0
  10. package/dist/resourcemgr/css/resourcemgr.css +1137 -97
  11. package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
  12. package/dist/resourcemgr/fileBrowser.js +456 -79
  13. package/dist/resourcemgr/filePreview.js +40 -4
  14. package/dist/resourcemgr/fileSelector.js +362 -92
  15. package/dist/resourcemgr.js +309 -57
  16. package/dist/searchModal/advancedSearch.js +233 -31
  17. package/package.json +15 -6
  18. package/src/image/ImgStateActive/initHelper.js +69 -31
  19. package/src/mediaplayer/players/html5.js +17 -7
  20. package/src/mediaplayer/readme.md +17 -0
  21. package/src/mediaplayer/tpl/audio.tpl +1 -1
  22. package/src/mediaplayer/tpl/video.tpl +1 -1
  23. package/src/mediaplayer/utils/crossOrigin.js +63 -0
  24. package/src/mediaplayer.js +10 -0
  25. package/src/previewer.js +1 -1
  26. package/src/resourcemgr/assetSearch.js +847 -0
  27. package/src/resourcemgr/assetSearchContract.js +489 -0
  28. package/src/resourcemgr/css/resourcemgr.css +1137 -97
  29. package/src/resourcemgr/css/resourcemgr.css.map +1 -1
  30. package/src/resourcemgr/fileBrowser.js +470 -91
  31. package/src/resourcemgr/filePreview.js +46 -4
  32. package/src/resourcemgr/fileSelector.js +338 -46
  33. package/src/resourcemgr/scss/resourcemgr.scss +1351 -88
  34. package/src/resourcemgr/tpl/advanced-search.tpl +10 -0
  35. package/src/resourcemgr/tpl/fileSelect.tpl +25 -28
  36. package/src/resourcemgr/tpl/folder.tpl +4 -1
  37. package/src/resourcemgr/tpl/layout.tpl +113 -54
  38. package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
  39. package/src/resourcemgr.js +289 -47
  40. package/src/searchModal/advancedSearch.js +242 -31
@@ -212,9 +212,9 @@ define(['jquery', 'nouislider', 'ui/resourcemgr', 'ui/tooltip', 'lodash', 'i18n'
212
212
  *
213
213
  * You should have received a copy of the GNU General Public License
214
214
  * along with this program; if not, write to the Free Software
215
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
215
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
216
216
  *
217
- * Copyright (c) 2021 (original work) Open Assessment Technologies SA ;
217
+ * Copyright (c) 2021-2026 (original work) Open Assessment Technologies SA ;
218
218
  *
219
219
  */
220
220
  const initAdvanced = function (widget) {
@@ -233,21 +233,45 @@ define(['jquery', 'nouislider', 'ui/resourcemgr', 'ui/tooltip', 'lodash', 'i18n'
233
233
  const $form = widget.$form;
234
234
  const options = widget.options;
235
235
  const {
236
- img
236
+ img,
237
+ $img
237
238
  } = getImage(widget);
238
239
  const $uploadTrigger = $form.find('[data-role="upload-trigger"]');
239
240
  const $src = $form.find('input[name=src]');
240
241
  const $alt = $form.find('input[name=alt]');
242
+ const commitSrc = function (srcValue) {
243
+ $src.val(srcValue);
244
+ // Keep QTI model + preview in sync immediately: the form src callback is
245
+ // throttled (1s), so a late trailing call must not leave a rejected pick.
246
+ if (img) {
247
+ img.attr('src', srcValue);
248
+ }
249
+ if ($img && $img.length) {
250
+ $img.attr('src', srcValue ? widget.getAssetManager().resolve(srcValue) : '');
251
+ $img.trigger('contentChange.qti-widget').change();
252
+ }
253
+ _.defer(function () {
254
+ if (img) {
255
+ img.attr('off-media-editor', 1);
256
+ }
257
+ $src.trigger('change');
258
+ });
259
+ };
241
260
  const _openResourceMgr = function () {
242
261
  $uploadTrigger.resourcemgr({
243
262
  title: __('Please select an image file from the resource manager. You can add files from your computer with the button "Add file(s)".'),
244
263
  appendContainer: options.mediaManager.appendContainer,
245
264
  mediaSourcesUrl: options.mediaManager.mediaSourcesUrl,
246
265
  browseUrl: options.mediaManager.browseUrl,
266
+ searchUrl: options.mediaManager.searchUrl,
247
267
  uploadUrl: options.mediaManager.uploadUrl,
248
268
  deleteUrl: options.mediaManager.deleteUrl,
249
269
  downloadUrl: options.mediaManager.downloadUrl,
250
270
  fileExistsUrl: options.mediaManager.fileExistsUrl,
271
+ rootClassUri: options.mediaManager.rootClassUri,
272
+ classMappingUrl: options.mediaManager.classMappingUrl,
273
+ statusUrl: options.mediaManager.statusUrl,
274
+ maxListSize: options.mediaManager.maxListSize,
251
275
  params: {
252
276
  uri: options.uri,
253
277
  lang: options.lang,
@@ -267,41 +291,49 @@ define(['jquery', 'nouislider', 'ui/resourcemgr', 'ui/tooltip', 'lodash', 'i18n'
267
291
  pathParam: 'path',
268
292
  path: options.mediaManager.path,
269
293
  root: options.mediaManager.root,
294
+ currentAsset: $src.val() || null,
270
295
  select: function (e, files) {
271
296
  let file, alt;
272
297
  let confirmBox, cancel, save;
273
- if (files && files.length) {
274
- file = files[0].file;
275
- alt = files[0].alt;
276
- $src.val(file);
277
- if ($$1.trim($alt.val()) === '') {
278
- if (alt === '') {
279
- alt = extractLabel(file);
280
- }
298
+ if (!(files && files.length)) {
299
+ return;
300
+ }
301
+ file = files[0].file;
302
+ alt = files[0].alt;
303
+
304
+ // Apply the new file immediately; alt confirm only decides alt text.
305
+ commitSrc(file);
306
+ if ($$1.trim($alt.val()) === '') {
307
+ if (alt === '') {
308
+ alt = extractLabel(file);
309
+ }
310
+ if (img) {
281
311
  img.attr('alt', alt);
282
- $alt.val(alt).trigger('change');
283
- } else {
284
- confirmBox = $$1('.change-alt-modal-feedback', $form);
285
- cancel = confirmBox.find('.cancel');
286
- save = confirmBox.find('.save');
287
- $$1('.alt-text', confirmBox).html(`"${$alt.val()}"<br>${__('with')}<br>"${alt}" ?`);
288
- confirmBox.modal({
289
- width: 500
290
- });
291
- save.off('click').on('click', function () {
292
- img.attr('alt', alt);
293
- $alt.val(alt).trigger('change');
294
- confirmBox.modal('close');
295
- });
296
- cancel.off('click').on('click', function () {
297
- confirmBox.modal('close');
298
- });
299
312
  }
300
- _.defer(function () {
301
- img.attr('off-media-editor', 1);
302
- $src.trigger('change');
303
- });
313
+ $alt.val(alt).trigger('change');
314
+ return;
304
315
  }
316
+
317
+ // Existing alt: confirm replace. Cancel / overlay / Esc keeps the new image;
318
+ // only alt text stays unchanged unless the user confirms.
319
+ confirmBox = $$1('.change-alt-modal-feedback', $form);
320
+ cancel = confirmBox.find('.cancel');
321
+ save = confirmBox.find('.save');
322
+ const $altText = $$1('.alt-text', confirmBox).empty();
323
+ $altText.append(document.createTextNode(`"${$alt.val()}"`)).append($$1('<br>')).append(document.createTextNode(__('with'))).append($$1('<br>')).append(document.createTextNode(`"${alt}" ?`));
324
+ confirmBox.modal({
325
+ width: 500
326
+ });
327
+ save.off('click').on('click', function () {
328
+ if (img) {
329
+ img.attr('alt', alt);
330
+ }
331
+ $alt.val(alt).trigger('change');
332
+ confirmBox.modal('close');
333
+ });
334
+ cancel.off('click').on('click', function () {
335
+ confirmBox.modal('close');
336
+ });
305
337
  },
306
338
  open: function () {
307
339
  // hide tooltip if displayed
@@ -210,9 +210,9 @@ define(['exports', 'jquery', 'lodash', 'i18n', 'core/mimetype', 'ui/mediaEditor/
210
210
  *
211
211
  * You should have received a copy of the GNU General Public License
212
212
  * along with this program; if not, write to the Free Software
213
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
213
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
214
214
  *
215
- * Copyright (c) 2021 (original work) Open Assessment Technologies SA ;
215
+ * Copyright (c) 2021-2026 (original work) Open Assessment Technologies SA ;
216
216
  *
217
217
  */
218
218
  const initAdvanced = function (widget) {
@@ -231,21 +231,45 @@ define(['exports', 'jquery', 'lodash', 'i18n', 'core/mimetype', 'ui/mediaEditor/
231
231
  const $form = widget.$form;
232
232
  const options = widget.options;
233
233
  const {
234
- img
234
+ img,
235
+ $img
235
236
  } = getImage(widget);
236
237
  const $uploadTrigger = $form.find('[data-role="upload-trigger"]');
237
238
  const $src = $form.find('input[name=src]');
238
239
  const $alt = $form.find('input[name=alt]');
240
+ const commitSrc = function (srcValue) {
241
+ $src.val(srcValue);
242
+ // Keep QTI model + preview in sync immediately: the form src callback is
243
+ // throttled (1s), so a late trailing call must not leave a rejected pick.
244
+ if (img) {
245
+ img.attr('src', srcValue);
246
+ }
247
+ if ($img && $img.length) {
248
+ $img.attr('src', srcValue ? widget.getAssetManager().resolve(srcValue) : '');
249
+ $img.trigger('contentChange.qti-widget').change();
250
+ }
251
+ _.defer(function () {
252
+ if (img) {
253
+ img.attr('off-media-editor', 1);
254
+ }
255
+ $src.trigger('change');
256
+ });
257
+ };
239
258
  const _openResourceMgr = function () {
240
259
  $uploadTrigger.resourcemgr({
241
260
  title: __('Please select an image file from the resource manager. You can add files from your computer with the button "Add file(s)".'),
242
261
  appendContainer: options.mediaManager.appendContainer,
243
262
  mediaSourcesUrl: options.mediaManager.mediaSourcesUrl,
244
263
  browseUrl: options.mediaManager.browseUrl,
264
+ searchUrl: options.mediaManager.searchUrl,
245
265
  uploadUrl: options.mediaManager.uploadUrl,
246
266
  deleteUrl: options.mediaManager.deleteUrl,
247
267
  downloadUrl: options.mediaManager.downloadUrl,
248
268
  fileExistsUrl: options.mediaManager.fileExistsUrl,
269
+ rootClassUri: options.mediaManager.rootClassUri,
270
+ classMappingUrl: options.mediaManager.classMappingUrl,
271
+ statusUrl: options.mediaManager.statusUrl,
272
+ maxListSize: options.mediaManager.maxListSize,
249
273
  params: {
250
274
  uri: options.uri,
251
275
  lang: options.lang,
@@ -265,41 +289,49 @@ define(['exports', 'jquery', 'lodash', 'i18n', 'core/mimetype', 'ui/mediaEditor/
265
289
  pathParam: 'path',
266
290
  path: options.mediaManager.path,
267
291
  root: options.mediaManager.root,
292
+ currentAsset: $src.val() || null,
268
293
  select: function (e, files) {
269
294
  let file, alt;
270
295
  let confirmBox, cancel, save;
271
- if (files && files.length) {
272
- file = files[0].file;
273
- alt = files[0].alt;
274
- $src.val(file);
275
- if ($.trim($alt.val()) === '') {
276
- if (alt === '') {
277
- alt = extractLabel(file);
278
- }
296
+ if (!(files && files.length)) {
297
+ return;
298
+ }
299
+ file = files[0].file;
300
+ alt = files[0].alt;
301
+
302
+ // Apply the new file immediately; alt confirm only decides alt text.
303
+ commitSrc(file);
304
+ if ($.trim($alt.val()) === '') {
305
+ if (alt === '') {
306
+ alt = extractLabel(file);
307
+ }
308
+ if (img) {
279
309
  img.attr('alt', alt);
280
- $alt.val(alt).trigger('change');
281
- } else {
282
- confirmBox = $('.change-alt-modal-feedback', $form);
283
- cancel = confirmBox.find('.cancel');
284
- save = confirmBox.find('.save');
285
- $('.alt-text', confirmBox).html(`"${$alt.val()}"<br>${__('with')}<br>"${alt}" ?`);
286
- confirmBox.modal({
287
- width: 500
288
- });
289
- save.off('click').on('click', function () {
290
- img.attr('alt', alt);
291
- $alt.val(alt).trigger('change');
292
- confirmBox.modal('close');
293
- });
294
- cancel.off('click').on('click', function () {
295
- confirmBox.modal('close');
296
- });
297
310
  }
298
- _.defer(function () {
299
- img.attr('off-media-editor', 1);
300
- $src.trigger('change');
301
- });
311
+ $alt.val(alt).trigger('change');
312
+ return;
302
313
  }
314
+
315
+ // Existing alt: confirm replace. Cancel / overlay / Esc keeps the new image;
316
+ // only alt text stays unchanged unless the user confirms.
317
+ confirmBox = $('.change-alt-modal-feedback', $form);
318
+ cancel = confirmBox.find('.cancel');
319
+ save = confirmBox.find('.save');
320
+ const $altText = $('.alt-text', confirmBox).empty();
321
+ $altText.append(document.createTextNode(`"${$alt.val()}"`)).append($('<br>')).append(document.createTextNode(__('with'))).append($('<br>')).append(document.createTextNode(`"${alt}" ?`));
322
+ confirmBox.modal({
323
+ width: 500
324
+ });
325
+ save.off('click').on('click', function () {
326
+ if (img) {
327
+ img.attr('alt', alt);
328
+ }
329
+ $alt.val(alt).trigger('change');
330
+ confirmBox.modal('close');
331
+ });
332
+ cancel.off('click').on('click', function () {
333
+ confirmBox.modal('close');
334
+ });
303
335
  },
304
336
  open: function () {
305
337
  // hide tooltip if displayed
@@ -210,9 +210,9 @@ define(['jquery', 'nouislider', 'ui/resourcemgr', 'ui/tooltip', 'lodash', 'i18n'
210
210
  *
211
211
  * You should have received a copy of the GNU General Public License
212
212
  * along with this program; if not, write to the Free Software
213
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
213
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
214
214
  *
215
- * Copyright (c) 2021 (original work) Open Assessment Technologies SA ;
215
+ * Copyright (c) 2021-2026 (original work) Open Assessment Technologies SA ;
216
216
  *
217
217
  */
218
218
  const initAdvanced = function (widget) {
@@ -231,21 +231,45 @@ define(['jquery', 'nouislider', 'ui/resourcemgr', 'ui/tooltip', 'lodash', 'i18n'
231
231
  const $form = widget.$form;
232
232
  const options = widget.options;
233
233
  const {
234
- img
234
+ img,
235
+ $img
235
236
  } = getImage(widget);
236
237
  const $uploadTrigger = $form.find('[data-role="upload-trigger"]');
237
238
  const $src = $form.find('input[name=src]');
238
239
  const $alt = $form.find('input[name=alt]');
240
+ const commitSrc = function (srcValue) {
241
+ $src.val(srcValue);
242
+ // Keep QTI model + preview in sync immediately: the form src callback is
243
+ // throttled (1s), so a late trailing call must not leave a rejected pick.
244
+ if (img) {
245
+ img.attr('src', srcValue);
246
+ }
247
+ if ($img && $img.length) {
248
+ $img.attr('src', srcValue ? widget.getAssetManager().resolve(srcValue) : '');
249
+ $img.trigger('contentChange.qti-widget').change();
250
+ }
251
+ _.defer(function () {
252
+ if (img) {
253
+ img.attr('off-media-editor', 1);
254
+ }
255
+ $src.trigger('change');
256
+ });
257
+ };
239
258
  const _openResourceMgr = function () {
240
259
  $uploadTrigger.resourcemgr({
241
260
  title: __('Please select an image file from the resource manager. You can add files from your computer with the button "Add file(s)".'),
242
261
  appendContainer: options.mediaManager.appendContainer,
243
262
  mediaSourcesUrl: options.mediaManager.mediaSourcesUrl,
244
263
  browseUrl: options.mediaManager.browseUrl,
264
+ searchUrl: options.mediaManager.searchUrl,
245
265
  uploadUrl: options.mediaManager.uploadUrl,
246
266
  deleteUrl: options.mediaManager.deleteUrl,
247
267
  downloadUrl: options.mediaManager.downloadUrl,
248
268
  fileExistsUrl: options.mediaManager.fileExistsUrl,
269
+ rootClassUri: options.mediaManager.rootClassUri,
270
+ classMappingUrl: options.mediaManager.classMappingUrl,
271
+ statusUrl: options.mediaManager.statusUrl,
272
+ maxListSize: options.mediaManager.maxListSize,
249
273
  params: {
250
274
  uri: options.uri,
251
275
  lang: options.lang,
@@ -265,41 +289,49 @@ define(['jquery', 'nouislider', 'ui/resourcemgr', 'ui/tooltip', 'lodash', 'i18n'
265
289
  pathParam: 'path',
266
290
  path: options.mediaManager.path,
267
291
  root: options.mediaManager.root,
292
+ currentAsset: $src.val() || null,
268
293
  select: function (e, files) {
269
294
  let file, alt;
270
295
  let confirmBox, cancel, save;
271
- if (files && files.length) {
272
- file = files[0].file;
273
- alt = files[0].alt;
274
- $src.val(file);
275
- if ($.trim($alt.val()) === '') {
276
- if (alt === '') {
277
- alt = extractLabel(file);
278
- }
296
+ if (!(files && files.length)) {
297
+ return;
298
+ }
299
+ file = files[0].file;
300
+ alt = files[0].alt;
301
+
302
+ // Apply the new file immediately; alt confirm only decides alt text.
303
+ commitSrc(file);
304
+ if ($.trim($alt.val()) === '') {
305
+ if (alt === '') {
306
+ alt = extractLabel(file);
307
+ }
308
+ if (img) {
279
309
  img.attr('alt', alt);
280
- $alt.val(alt).trigger('change');
281
- } else {
282
- confirmBox = $('.change-alt-modal-feedback', $form);
283
- cancel = confirmBox.find('.cancel');
284
- save = confirmBox.find('.save');
285
- $('.alt-text', confirmBox).html(`"${$alt.val()}"<br>${__('with')}<br>"${alt}" ?`);
286
- confirmBox.modal({
287
- width: 500
288
- });
289
- save.off('click').on('click', function () {
290
- img.attr('alt', alt);
291
- $alt.val(alt).trigger('change');
292
- confirmBox.modal('close');
293
- });
294
- cancel.off('click').on('click', function () {
295
- confirmBox.modal('close');
296
- });
297
310
  }
298
- _.defer(function () {
299
- img.attr('off-media-editor', 1);
300
- $src.trigger('change');
301
- });
311
+ $alt.val(alt).trigger('change');
312
+ return;
302
313
  }
314
+
315
+ // Existing alt: confirm replace. Cancel / overlay / Esc keeps the new image;
316
+ // only alt text stays unchanged unless the user confirms.
317
+ confirmBox = $('.change-alt-modal-feedback', $form);
318
+ cancel = confirmBox.find('.cancel');
319
+ save = confirmBox.find('.save');
320
+ const $altText = $('.alt-text', confirmBox).empty();
321
+ $altText.append(document.createTextNode(`"${$alt.val()}"`)).append($('<br>')).append(document.createTextNode(__('with'))).append($('<br>')).append(document.createTextNode(`"${alt}" ?`));
322
+ confirmBox.modal({
323
+ width: 500
324
+ });
325
+ save.off('click').on('click', function () {
326
+ if (img) {
327
+ img.attr('alt', alt);
328
+ }
329
+ $alt.val(alt).trigger('change');
330
+ confirmBox.modal('close');
331
+ });
332
+ cancel.off('click').on('click', function () {
333
+ confirmBox.modal('close');
334
+ });
303
335
  },
304
336
  open: function () {
305
337
  // hide tooltip if displayed
@@ -1,4 +1,4 @@
1
- define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support', 'handlebars', 'lib/handlebars/helpers', 'ui/mediaplayer/utils/reminder', 'ui/mediaplayer/utils/timeObserver'], function ($$1, UrlParser, eventifier, support, Handlebars, Helpers0, reminderManagerFactory, timeObserverFactory) { 'use strict';
1
+ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support', 'handlebars', 'lib/handlebars/helpers', 'ui/mediaplayer/utils/reminder', 'ui/mediaplayer/utils/timeObserver', 'ui/mediaplayer/utils/crossOrigin'], function ($$1, UrlParser, eventifier, support, Handlebars, Helpers0, reminderManagerFactory, timeObserverFactory, crossOriginUtil) { 'use strict';
2
2
 
3
3
  $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
4
4
  UrlParser = UrlParser && Object.prototype.hasOwnProperty.call(UrlParser, 'default') ? UrlParser['default'] : UrlParser;
@@ -8,6 +8,7 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
8
8
  Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
9
9
  reminderManagerFactory = reminderManagerFactory && Object.prototype.hasOwnProperty.call(reminderManagerFactory, 'default') ? reminderManagerFactory['default'] : reminderManagerFactory;
10
10
  timeObserverFactory = timeObserverFactory && Object.prototype.hasOwnProperty.call(timeObserverFactory, 'default') ? timeObserverFactory['default'] : timeObserverFactory;
11
+ crossOriginUtil = crossOriginUtil && Object.prototype.hasOwnProperty.call(crossOriginUtil, 'default') ? crossOriginUtil['default'] : crossOriginUtil;
11
12
 
12
13
  if (!Helpers0.__initialized) {
13
14
  Helpers0(Handlebars);
@@ -16,16 +17,29 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
16
17
  var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
17
18
  this.compilerInfo = [4,'>= 1.0.0'];
18
19
  helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
19
- var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;
20
+ var buffer = "", stack1, helper, options, self=this, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;
20
21
 
21
22
  function program1(depth0,data) {
22
23
 
23
24
 
24
- return "crossorigin";
25
+ return " crossorigin=\"use-credentials\"";
25
26
  }
26
27
 
27
28
  function program3(depth0,data) {
28
29
 
30
+ var stack1;
31
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOrigin), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
32
+ if(stack1 || stack1 === 0) { return stack1; }
33
+ else { return ''; }
34
+ }
35
+ function program4(depth0,data) {
36
+
37
+
38
+ return " crossorigin";
39
+ }
40
+
41
+ function program6(depth0,data) {
42
+
29
43
  var buffer = "", stack1, helper, options;
30
44
  buffer += "\n <a href=\"";
31
45
  if (helper = helpers.link) { stack1 = helper.call(depth0, {hash:{},data:data}); }
@@ -41,13 +55,13 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
41
55
  if (helper = helpers.preload) { stack1 = helper.call(depth0, {hash:{},data:data}); }
42
56
  else { helper = (depth0 && depth0.preload); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
43
57
  buffer += escapeExpression(stack1)
44
- + "\" controls ";
45
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.cors), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
58
+ + "\" controls";
59
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOriginUseCredentials), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
46
60
  if(stack1 || stack1 === 0) { buffer += stack1; }
47
61
  buffer += ">\n "
48
62
  + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Your browser doesn’t support the audio player.", options) : helperMissing.call(depth0, "__", "Your browser doesn’t support the audio player.", options)))
49
63
  + "\n";
50
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
64
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(6, program6, data),data:data});
51
65
  if(stack1 || stack1 === 0) { buffer += stack1; }
52
66
  buffer += "\n</audio>\n";
53
67
  return buffer;
@@ -64,16 +78,29 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
64
78
  var Template$1 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
65
79
  this.compilerInfo = [4,'>= 1.0.0'];
66
80
  helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
67
- var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;
81
+ var buffer = "", stack1, helper, options, self=this, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;
68
82
 
69
83
  function program1(depth0,data) {
70
84
 
71
85
 
72
- return "crossorigin";
86
+ return " crossorigin=\"use-credentials\"";
73
87
  }
74
88
 
75
89
  function program3(depth0,data) {
76
90
 
91
+ var stack1;
92
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOrigin), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
93
+ if(stack1 || stack1 === 0) { return stack1; }
94
+ else { return ''; }
95
+ }
96
+ function program4(depth0,data) {
97
+
98
+
99
+ return " crossorigin";
100
+ }
101
+
102
+ function program6(depth0,data) {
103
+
77
104
  var buffer = "", stack1, helper, options;
78
105
  buffer += "\n <a href=\"";
79
106
  if (helper = helpers.link) { stack1 = helper.call(depth0, {hash:{},data:data}); }
@@ -93,13 +120,13 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
93
120
  if (helper = helpers.poster) { stack1 = helper.call(depth0, {hash:{},data:data}); }
94
121
  else { helper = (depth0 && depth0.poster); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
95
122
  buffer += escapeExpression(stack1)
96
- + "\" controls ";
97
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.cors), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
123
+ + "\" controls";
124
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOriginUseCredentials), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
98
125
  if(stack1 || stack1 === 0) { buffer += stack1; }
99
126
  buffer += ">\n "
100
127
  + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Your browser doesn’t support the video player.", options) : helperMissing.call(depth0, "__", "Your browser doesn’t support the video player.", options)))
101
128
  + "\n";
102
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
129
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(6, program6, data),data:data});
103
130
  if(stack1 || stack1 === 0) { buffer += stack1; }
104
131
  buffer += "\n</video>\n";
105
132
  return buffer;
@@ -148,9 +175,9 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
148
175
  *
149
176
  * You should have received a copy of the GNU General Public License
150
177
  * along with this program; if not, write to the Free Software
151
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
178
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
152
179
  *
153
- * Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
180
+ * Copyright (c) 2015-2026 (original work) Open Assessment Technologies SA ;
154
181
  */
155
182
 
156
183
  /**
@@ -191,6 +218,7 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
191
218
  * @param {string} [config.type] - The type of player (video or audio) (default: video)
192
219
  * @param {boolean} [config.preview] - Enables the media preview (load media metadata)
193
220
  * @param {boolean} [config.debug] - Enables the debug mode
221
+ * @param {'anonymous'|'use-credentials'} [config.crossOriginMode] - CORS credentials mode for cross-domain sources
194
222
  * @param {number} [config.config.stalledDetectionDelay] - The delay before considering a media is stalled
195
223
  * @returns {object} player
196
224
  */
@@ -253,16 +281,12 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
253
281
  init() {
254
282
  const tpl = 'audio' === type ? audioTpl : videoTpl;
255
283
  const page = new UrlParser(window.location);
256
- let cors = false;
257
284
  let preload = config.preview ? 'metadata' : 'none';
258
285
  let poster = '';
259
286
  let link = '';
260
287
  let result = false;
261
288
  state = {};
262
289
  sources.forEach(source => {
263
- if (!page.sameDomain(source.src)) {
264
- cors = true;
265
- }
266
290
  if (source.poster) {
267
291
  poster = source.poster;
268
292
  }
@@ -270,8 +294,10 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
270
294
  link = source.link;
271
295
  }
272
296
  });
297
+ const crossOriginAttr = crossOriginUtil.resolveCrossOriginAttribute(sources, config.crossOriginMode, page);
273
298
  $media = $$1(tpl({
274
- cors,
299
+ crossOrigin: crossOriginAttr === 'anonymous',
300
+ crossOriginUseCredentials: crossOriginAttr === 'use-credentials',
275
301
  preload,
276
302
  poster,
277
303
  link
@@ -0,0 +1,64 @@
1
+ define(function () { 'use strict';
2
+
3
+ /**
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License
6
+ * as published by the Free Software Foundation; under version 2
7
+ * of the License (non-upgradable).
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+ *
18
+ * Copyright (c) 2026 (original work) Open Assessment Technologies SA ;
19
+ */
20
+
21
+ /** @typedef {'anonymous' | 'use-credentials'} CrossOriginMode */
22
+
23
+ const VALID_CROSS_ORIGIN_MODES = new Set(['anonymous', 'use-credentials']);
24
+
25
+ /**
26
+ * Normalises cross-origin mode from instance or RequireJS module config.
27
+ * @param {CrossOriginMode | string | undefined | null} instanceMode
28
+ * @param {CrossOriginMode | string | undefined | null} [moduleMode]
29
+ * @returns {CrossOriginMode}
30
+ */
31
+ function resolveCrossOriginMode(instanceMode, moduleMode) {
32
+ let mode = instanceMode;
33
+ if (typeof mode === 'undefined' || mode === null) {
34
+ mode = moduleMode;
35
+ }
36
+ if (typeof mode === 'undefined' || mode === null) {
37
+ mode = 'anonymous';
38
+ }
39
+ return VALID_CROSS_ORIGIN_MODES.has(mode) ? /** @type {CrossOriginMode} */mode : 'anonymous';
40
+ }
41
+
42
+ /**
43
+ * Resolves the crossorigin attribute for HTML5 media when sources are cross-domain.
44
+ * @param {Array<{ src?: string }>} sources
45
+ * @param {CrossOriginMode} crossOriginMode
46
+ * @param {{ sameDomain: (url: string) => boolean }} page
47
+ * @returns {CrossOriginMode | null} null when attribute should be omitted
48
+ */
49
+ function resolveCrossOriginAttribute(sources, crossOriginMode, page) {
50
+ const isCrossDomain = sources.some(source => source && source.src && !page.sameDomain(source.src));
51
+ if (!isCrossDomain) {
52
+ return null;
53
+ }
54
+ return resolveCrossOriginMode(crossOriginMode);
55
+ }
56
+ var crossOrigin = {
57
+ VALID_CROSS_ORIGIN_MODES,
58
+ resolveCrossOriginMode,
59
+ resolveCrossOriginAttribute
60
+ };
61
+
62
+ return crossOrigin;
63
+
64
+ });