diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index a21b8a928b6b..0a7c546271c7 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -47,6 +47,8 @@ function($, _, Backbone, gettext, BasePage, clipboardData: { content: null }, }, + targetXBlock: null, + view: 'container_preview', defaultViewClass: ContainerView, @@ -130,10 +132,26 @@ function($, _, Backbone, gettext, BasePage, if (this.options.isIframeEmbed) { window.addEventListener('message', (event) => { - if (event.data && event.data.type === 'refreshXBlock') { - this.render(); - } - }); + const { data } = event; + + if (!data) return; + + const xblockElement = this.findXBlockElement(this.targetXBlock); + + switch (data.type) { + case 'refreshXBlock': + this.render(); + break; + case 'completeManageXBlockAccess': + this.refreshXBlock(xblockElement, false); + break; + case 'addXBlock': + this.createComponent(this, xblockElement, event.data); + break; + default: + console.warn('Unhandled message type:', data.type); + } + }); } this.listenTo(Backbone, 'move:onXBlockMoved', this.onXBlockMoved); @@ -394,8 +412,18 @@ function($, _, Backbone, gettext, BasePage, const isAccessButton = event.currentTarget.className === 'access-button'; const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions'); const usageId = encodeURI(primaryHeader.attr('data-usage-id')); + + this.targetXBlock = event.target; + try { if (this.options.isIframeEmbed && isAccessButton) { + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { courseXBlockDropdownHeight: 0 } + }, document.referrer + ); return window.parent.postMessage( { type: 'manageXBlockAccess', @@ -734,13 +762,20 @@ function($, _, Backbone, gettext, BasePage, const usageId = encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed) { - return window.parent.postMessage( + window.parent.postMessage( { type: 'duplicateXBlock', message: 'Duplicate the XBlock', payload: { blockType, usageId } }, document.referrer ); + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { courseXBlockDropdownHeight: 0 } + }, document.referrer + ); } } catch (e) { console.error(e); @@ -778,6 +813,13 @@ function($, _, Backbone, gettext, BasePage, }, }, document.referrer ); + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { courseXBlockDropdownHeight: 0 } + }, document.referrer + ); return true; } } catch (e) { @@ -794,13 +836,20 @@ function($, _, Backbone, gettext, BasePage, const usageId = encodeURI(primaryHeader.attr('data-usage-id')); try { if (this.options.isIframeEmbed) { - return window.parent.postMessage( + window.parent.postMessage( { type: 'deleteXBlock', message: 'Delete the XBlock', payload: { usageId } }, document.referrer ); + window.parent.postMessage( + { + type: 'toggleCourseXBlockDropdown', + message: 'Adjust the height of the dropdown menu', + payload: { courseXBlockDropdownHeight: 0 } + }, document.referrer + ); } } catch (e) { console.error(e); @@ -812,21 +861,38 @@ function($, _, Backbone, gettext, BasePage, return $('
', {class: 'studio-xblock-wrapper'}); }, - createComponent: function(template, target) { + createComponent: function(template, target, iframeMessageData) { // A placeholder element is created in the correct location for the new xblock // and then onNewXBlock will replace it with a rendering of the xblock. Note that // for xblocks that can't be replaced inline, the entire parent will be refreshed. var parentElement = this.findXBlockElement(target), parentLocator = parentElement.data('locator'), - buttonPanel = target.closest('.add-xblock-component'), - listPanel = buttonPanel.prev(), - scrollOffset = ViewUtils.getScrollOffset(buttonPanel), + buttonPanel = target?.closest('.add-xblock-component'), + listPanel = buttonPanel?.prev(), $placeholderEl = $(this.createPlaceholderElement()), requestData = _.extend(template, { parent_locator: parentLocator }), - placeholderElement; - placeholderElement = $placeholderEl.appendTo(listPanel); + scrollOffset, + placeholderElement, + $container; + + if (this.options.isIframeEmbed) { + $container = $('ol.reorderable-container.ui-sortable'); + scrollOffset = 0; + } else { + $container = listPanel; + scrollOffset = ViewUtils.getScrollOffset(buttonPanel); + } + + placeholderElement = $placeholderEl.appendTo($container); + + if (this.options.isIframeEmbed) { + if (iframeMessageData.payload.data && iframeMessageData.type === 'addXBlock') { + return this.onNewXBlock(placeholderElement, scrollOffset, false, iframeMessageData.payload.data); + } + } + return $.postJSON(this.getURLRoot() + '/', requestData, _.bind(this.onNewXBlock, this, placeholderElement, scrollOffset, false)) .fail(function() { @@ -846,6 +912,26 @@ function($, _, Backbone, gettext, BasePage, placeholderElement; placeholderElement = $placeholderEl.insertAfter(xblockElement); + + if (this.options.isIframeEmbed) { + try { + window.parent.postMessage( + { + type: 'scrollToXBlock', + message: 'Scroll to XBlock', + payload: { scrollOffset } + }, document.referrer + ); + } catch (e) { + console.error(e); + } + return window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'completeXBlockDuplicating') { + return self.onNewXBlock(placeholderElement, scrollOffset, true, event.data.payload); + } + }); + } + XBlockUtils.duplicateXBlock(xblockElement, parentElement) .done(function(data) { self.onNewXBlock(placeholderElement, scrollOffset, true, data); @@ -861,6 +947,15 @@ function($, _, Backbone, gettext, BasePage, xblockInfo = new XBlockInfo({ id: xblockElement.data('locator') }); + + if (this.options.isIframeEmbed) { + return window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'completeXBlockDeleting') { + return self.onDelete(xblockElement); + } + }); + } + XBlockUtils.deleteXBlock(xblockInfo).done(function() { self.onDelete(xblockElement); }); @@ -969,7 +1064,9 @@ function($, _, Backbone, gettext, BasePage, window.location.href = destinationUrl; return; } - ViewUtils.setScrollOffset(xblockElement, scrollOffset); + if (!this.options.isIframeEmbed) { + ViewUtils.setScrollOffset(xblockElement, scrollOffset); + } xblockElement.data('locator', data.locator); return this.refreshXBlock(xblockElement, true, is_duplicate); }, @@ -986,7 +1083,9 @@ function($, _, Backbone, gettext, BasePage, parentElement = xblockElement.parent(), rootLocator = this.xblockView.model.id; if (xblockElement.length === 0 || xblockElement.data('locator') === rootLocator) { - this.render({refresh: true, block_added: block_added}); + if (block_added) { + this.render({refresh: true, block_added: block_added}); + } } else if (parentElement.hasClass('reorderable-container')) { this.refreshChildXBlock(xblockElement, block_added, is_duplicate); } else {