Files
edx-platform/cms/static/js/views/modals/edit_xblock.js
Rômulo Penido 85e81b32e4 feat: add full screen modal option to base_modal (#38001)
Adds a "Fullscreen" button to the iframe editors for advanced XBlocks
2026-02-27 16:12:04 +00:00

302 lines
11 KiB
JavaScript

/**
* The EditXBlockModal is a Backbone view that shows an xblock editor in a modal window.
* It is invoked using the edit method which is passed an existing rendered xblock,
* and upon save an optional refresh function can be invoked to update the display.
*/
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_modal',
'common/js/components/utils/view_utils', 'js/views/utils/xblock_utils', 'js/views/xblock_editor'],
function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockEditorView) {
'use strict';
var EditXBlockModal = BaseModal.extend({
events: _.extend({}, BaseModal.prototype.events, {
'click .action-save': 'save',
'click .action-modes a': 'changeMode',
'click .title-edit-button': 'clickTitleButton'
}),
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'edit-xblock',
view: 'studio_view',
viewSpecificClasses: 'modal-editor confirm',
// Translators: "title" is the name of the current component being edited.
titleFormat: gettext('Editing: {title}'),
addPrimaryActionButton: true,
showFullscreenButton: true,
}),
initialize: function() {
BaseModal.prototype.initialize.call(this);
this.template = this.loadTemplate('edit-xblock-modal');
this.editorModeButtonTemplate = this.loadTemplate('editor-mode-button');
},
/**
* Show an edit modal for the specified xblock
* @param xblockElement The element that contains the xblock to be edited.
* @param rootXBlockInfo An XBlockInfo model that describes the root xblock on the page.
* @param options A standard options object.
*/
edit: function(xblockElement, rootXBlockInfo, options) {
this.xblockElement = xblockElement;
this.xblockInfo = XBlockViewUtils.findXBlockInfo(xblockElement, rootXBlockInfo);
this.options.modalType = this.xblockInfo.get('category');
this.editOptions = options;
this.render();
this.show();
// Hide the action bar until we know which buttons we want
this.getActionBar().hide();
// Display the xblock after the modal is shown as there are some xblocks
// that depend upon being visible when they initialize, e.g. the problem xmodule.
this.displayXBlock();
},
getContentHtml: function() {
return this.template({
xblockInfo: this.xblockInfo
});
},
displayXBlock: function() {
this.editorView = new XBlockEditorView({
el: this.$('.xblock-editor'),
model: this.xblockInfo,
view: this.options.view
});
this.editorView.render({
success: _.bind(this.onDisplayXBlock, this)
});
},
createTitleEditor: function(title) {
// xss-lint: disable=javascript-jquery-html
this.$('.modal-window-title').html(this.loadTemplate('edit-title-button')({title: title}));
},
createWarningToast: function(upstreamLink) {
// xss-lint: disable=javascript-jquery-insertion
this.$('.modal-header').before(this.loadTemplate('edit-upstream-alert')({
upstreamLink: upstreamLink,
}));
},
getXBlockUpstreamLink: function() {
if (!this.xblockElement || !this.xblockElement.length) {
console.error('xblockElement is empty or not defined');
return;
}
const usageKey = this.xblockElement.data('locator');
$.ajax({
url: '/api/contentstore/v2/downstreams/' + usageKey,
type: 'GET',
success: function(data) {
if (data?.upstream_link) {
this.createWarningToast(data.upstream_link);
}
}.bind(this),
notifyOnError: false,
})
},
onDisplayXBlock: function() {
var editorView = this.editorView,
title = this.getTitle(),
readOnlyView = (this.editOptions && this.editOptions.readOnlyView) || !this.canSave();
// Notify the runtime that the modal has been shown
editorView.notifyRuntime('modal-shown', this);
// Update the modal's header
if (editorView.hasCustomTabs()) {
// Hide the modal's header as the custom editor provides its own
this.$('.modal-header').hide();
// Update the custom editor's title
editorView.$('.component-name').text(title);
} else if (editorView.getDataEditor() && editorView.getMetadataEditor()) {
this.createTitleEditor(title);
this.addDefaultModes();
// If the plugins content element exists, add a button to reveal it.
if (this.$('.wrapper-comp-plugins').length > 0) {
this.addModeButton('plugins', gettext('Plugins'));
}
this.selectMode(editorView.mode);
} else {
this.$('.modal-window-title').text(title);
}
this.getXBlockUpstreamLink();
// If the xblock is not using custom buttons then choose which buttons to show
if (!editorView.hasCustomButtons()) {
// If the xblock does not support save then disable the save button
if (readOnlyView) {
this.disableSave();
}
this.getActionBar().show();
}
this.resize();
},
canSave: function() {
return this.editorView.xblock.save || this.editorView.xblock.collectFieldData;
},
disableSave: function() {
var saveButton = this.getActionButton('save'),
cancelButton = this.getActionButton('cancel');
saveButton.parent().hide();
cancelButton.text(gettext('Close'));
cancelButton.addClass('action-primary');
},
getTitle: function() {
var displayName = this.xblockInfo.get('display_name');
if (!displayName) {
if (this.xblockInfo.isVertical()) {
displayName = gettext('Unit');
} else {
displayName = gettext('Component');
}
}
return edx.StringUtils.interpolate(
this.options.titleFormat, {
title: displayName
}
);
},
addDefaultModes: function() {
var defaultModes, i, mode;
defaultModes = this.editorView.getDefaultModes();
for (i = 0; i < defaultModes.length; i++) {
mode = defaultModes[i];
this.addModeButton(mode.id, mode.name);
}
},
changeMode: function(event) {
var $parent = $(event.target.parentElement),
mode = $parent.data('mode');
event.preventDefault();
this.selectMode(mode);
},
selectMode: function(mode) {
var editorView = this.editorView,
buttonSelector;
editorView.selectMode(mode);
this.$('.editor-modes a').removeClass('is-set');
if (mode) {
buttonSelector = '.' + mode + '-button';
this.$(buttonSelector).addClass('is-set');
}
},
save: function(event) {
var self = this,
editorView = this.editorView,
xblockInfo = this.xblockInfo,
data = null;
try {
data = editorView.getXBlockFieldData();
} catch (e) {
ViewUtils.showErrorMeassage(
gettext("Studio's having trouble parsing the problem component's content"),
e.message,
10000
);
ViewUtils.setScrollOffset(editorView.$el, 100);
return null;
}
event.preventDefault();
if (data) {
ViewUtils.runOperationShowingMessage(gettext('Saving'),
function() {
return xblockInfo.save(data);
}).done(function() {
self.onSave();
});
}
return null;
},
onSave: function() {
try {
window.parent.postMessage({
type: 'saveEditedXBlockData',
message: 'Sends a message when the xblock data is saved',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}
var refresh = this.editOptions.refresh;
this.hide();
if (refresh) {
refresh(this.xblockInfo);
}
},
hide: function() {
// Notify child views to stop listening events
Backbone.trigger('xblock:editorModalHidden');
try {
window.parent.postMessage({
type: 'closeXBlockEditorModal',
message: 'Sends a message when the modal window is closed',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}
BaseModal.prototype.hide.call(this);
// Notify the runtime that the modal has been hidden
this.editorView.notifyRuntime('modal-hidden');
},
addModeButton: function(mode, displayName) {
var buttonPanel = this.$('.editor-modes');
// xss-lint: disable=javascript-jquery-append
buttonPanel.append(this.editorModeButtonTemplate({
mode: mode,
displayName: displayName
}));
},
clickTitleButton: function(event) {
var self = this,
oldTitle = this.xblockInfo.get('display_name'),
titleElt = this.$('.modal-window-title'),
$input = $('<input type="text" size="40" />'),
changeFunc = function(evt) {
var newTitle = $(evt.target).val();
if (oldTitle !== newTitle) {
self.xblockInfo.set('display_name', newTitle);
self.xblockInfo.save({metadata: {display_name: newTitle}});
}
self.createTitleEditor(self.getTitle());
return true;
};
event.preventDefault();
$input.val(oldTitle);
$input.change(changeFunc).blur(changeFunc);
titleElt.html($input); // xss-lint: disable=javascript-jquery-html
$input.focus().select();
$(event.target).remove();
return true;
},
});
return EditXBlockModal;
});