diff --git a/cms/djangoapps/contentstore/views/block.py b/cms/djangoapps/contentstore/views/block.py index de70e8ce5fe6..8104b09ada70 100644 --- a/cms/djangoapps/contentstore/views/block.py +++ b/cms/djangoapps/contentstore/views/block.py @@ -9,6 +9,7 @@ from django.db import transaction from django.http import Http404, HttpResponse from django.utils.translation import gettext as _ +from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.http import require_http_methods from opaque_keys.edx.keys import CourseKey from web_fragments.fragment import Fragment @@ -301,20 +302,27 @@ def xblock_view_handler(request, usage_key_string, view_name): return HttpResponse(status=406) +@xframe_options_exempt @require_http_methods("GET") @login_required -def edit_view_xblock(request, usage_key_string): +def xblock_actions_view(request, usage_key_string, action_name): """ - The handler for rendered edit xblock view. + Return rendered xblock action view. + The action name should be provided as an argument. + Valid options for action names are edit and move. """ usage_key = usage_key_with_run(usage_key_string) if not has_studio_read_access(request.user, usage_key.course_key): raise PermissionDenied() + if action_name not in ['edit', 'move']: + return HttpResponse(status=404) + store = modulestore() with store.bulk_operations(usage_key.course_key): course, xblock, lms_link, preview_lms_link = _get_item_in_course(request, usage_key) container_handler_context = get_container_handler_context(request, usage_key, course, xblock) + container_handler_context.update({'action_name': action_name}) return render_to_response('container_editor.html', container_handler_context) diff --git a/cms/static/js/views/modals/base_modal.js b/cms/static/js/views/modals/base_modal.js index 65b7f06ae021..828289533a73 100644 --- a/cms/static/js/views/modals/base_modal.js +++ b/cms/static/js/views/modals/base_modal.js @@ -119,6 +119,10 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'], event.preventDefault(); event.stopPropagation(); // Make sure parent modals don't see the click } + window.parent.postMessage({ + method: 'close_modal', + msg: 'Sends a message when the modal window is closed' + }, '*'); this.hide(); }, diff --git a/cms/static/js/views/modals/edit_xblock.js b/cms/static/js/views/modals/edit_xblock.js index 7182d8b0e51a..0ced05cea737 100644 --- a/cms/static/js/views/modals/edit_xblock.js +++ b/cms/static/js/views/modals/edit_xblock.js @@ -208,6 +208,11 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE // Notify child views to stop listening events Backbone.trigger('xblock:editorModalHidden'); + window.parent.postMessage({ + method: 'close_modal', + msg: 'Sends a message when the edit modal window is closed' + }, '*'); + BaseModal.prototype.hide.call(this); // Notify the runtime that the modal has been hidden diff --git a/cms/static/js/views/utils/move_xblock_utils.js b/cms/static/js/views/utils/move_xblock_utils.js index 9bde9c6d087e..f3bcc675f1da 100644 --- a/cms/static/js/views/utils/move_xblock_utils.js +++ b/cms/static/js/views/utils/move_xblock_utils.js @@ -26,8 +26,25 @@ function($, _, Backbone, Feedback, AlertView, XBlockViewUtils, MoveXBlockUtils, .done(function(response) { // hide modal Backbone.trigger('move:hideMoveModal'); - // hide xblock element - data.sourceXBlockElement.hide(); + if (data.sourceXBlockElement) { + // hide xblock element + data.sourceXBlockElement.hide(); + } + + window.parent.postMessage({ + method: 'move_xblock', + msg: 'Sends a message when the xblock is moved', + params: { + sourceDisplayName: data.sourceDisplayName, + sourceLocator: data.sourceLocator, + targetParentLocator: data.targetParentLocator, + } + }, '*'); + window.parent.postMessage({ + method: 'close_modal', + msg: 'Sends a message when the modal window is closed' + }, '*'); + showMovedNotification( StringUtils.interpolate( gettext('Success! "{displayName}" has been moved.'), @@ -36,7 +53,7 @@ function($, _, Backbone, Feedback, AlertView, XBlockViewUtils, MoveXBlockUtils, } ), { - sourceXBlockElement: data.sourceXBlockElement, + sourceXBlockElement: data.sourceXBlockElement ? data.sourceXBlockElement : null, sourceDisplayName: data.sourceDisplayName, sourceLocator: data.sourceLocator, sourceParentLocator: data.sourceParentLocator, @@ -78,7 +95,7 @@ function($, _, Backbone, Feedback, AlertView, XBlockViewUtils, MoveXBlockUtils, click: function() { undoMoveXBlock( { - sourceXBlockElement: data.sourceXBlockElement, + sourceXBlockElement: data.sourceXBlockElement ? data.sourceXBlockElement : null, sourceDisplayName: data.sourceDisplayName, sourceLocator: data.sourceLocator, sourceParentLocator: data.sourceParentLocator, diff --git a/cms/templates/container_editor.html b/cms/templates/container_editor.html index a5afa5125e62..43f23d2100a4 100644 --- a/cms/templates/container_editor.html +++ b/cms/templates/container_editor.html @@ -150,68 +150,6 @@ <%block name="content"> - - - - - - - - - - ${_("Edit")} - - - - - - - - %block> @@ -250,64 +188,32 @@ }); <%static:webpack entry="js/factories/container"> - ContainerFactory( - ${component_templates | n, dump_js_escaped_json}, - ${xblock_info | n, dump_js_escaped_json}, - '${action | n, js_escaped_string}', - { - isUnitPage: ${is_unit_page | n, dump_js_escaped_json}, - canEdit: true, - outlineURL: '${outline_url | n, js_escaped_string}', - clipboardData: ${user_clipboard | n, dump_js_escaped_json}, - } - ); - require(['js/models/xblock_info', 'js/views/xblock', 'js/views/utils/xblock_utils', 'common/js/components/utils/view_utils', 'gettext'], function (XBlockInfo, XBlockView, XBlockUtils, ViewUtils, gettext) { - var model = new XBlockInfo({ id: '${subsection.location|n, decode.utf8}' }); - var xblockView = new XBlockView({ - model: model, - el: $('#sequence-nav'), - view: 'author_view?position=${position|n, decode.utf8}&next_url=${next_url|n, decode.utf8}&prev_url=${prev_url|n, decode.utf8}', - clipboardData: ${user_clipboard | n, dump_js_escaped_json}, - }); - - xblockView.xblockReady = function() { - var toggleCaretButton = function(clipboardData) { - if (clipboardData && clipboardData.content && clipboardData.source_usage_key.includes('vertical')) { - $('.dropdown-toggle-button').show(); - } else { - $('.dropdown-toggle-button').hide(); - $('.dropdown-options').hide(); - } - }; - this.clipboardBroadcastChannel = new BroadcastChannel('studio_clipboard_channel'); - this.clipboardBroadcastChannel.onmessage = (event) => toggleCaretButton(event.data); - toggleCaretButton(this.options.clipboardData); - - $('#new-unit-button').on('click', function(event) { - event.preventDefault(); - XBlockUtils.addXBlock($(this)).done(function(locator) { - ViewUtils.redirect('/container/' + locator + '?action=new'); - }); - }); - - $('.custom-dropdown .dropdown-toggle-button').on('click', function(event) { - event.stopPropagation(); // Prevent the event from closing immediately when we open it - $(this).next('.dropdown-options').slideToggle('fast'); // This toggles the dropdown visibility - var isExpanded = $(this).attr('aria-expanded') === 'true'; - $(this).attr('aria-expanded', !isExpanded); + require(['js/models/xblock_info', 'js/views/xblock', 'js/views/utils/xblock_utils', +'common/js/components/utils/view_utils', 'gettext', 'js/views/modals/edit_xblock', 'js/views/modals/move_xblock_modal'], +function (XBlockInfo, XBlockView, XBlockUtils, ViewUtils, gettext, EditXBlockModal, MoveXBlockModal) { + function showMoveModal(unitLocation, xblockInfo, outlineUrl) { + var parentModel = new XBlockInfo({ id: unitLocation, category: 'vertical' }); + var moveModal = new MoveXBlockModal({ + sourceXBlockInfo: new XBlockInfo(xblockInfo), + sourceParentXBlockInfo: parentModel, + XBlockURLRoot: '/xblock', + outlineURL: outlineUrl, }); + moveModal.show(); + } - $('.seq_paste_unit').on('click', function(event) { - event.preventDefault(); - $('.dropdown-options').hide(); - XBlockUtils.pasteXBlock($(this)).done(function(data) { - ViewUtils.redirect('/container/' + data.locator + '?action=new'); - }); - }); - }; + function showEditModal(xblockInfo) { + var editModal = new EditXBlockModal(); + editModal.edit([], new XBlockInfo(xblockInfo), {}); + } - xblockView.render(); + var actionName = '${action_name|n, decode.utf8}'; + if (actionName === 'move') { + showMoveModal('${unit.location|n, decode.utf8}', ${xblock_info | n, dump_js_escaped_json}, '${outline_url | n, js_escaped_string}'); + } else if (actionName === 'edit') { + showEditModal(${xblock_info | n, dump_js_escaped_json}); + } }); %static:webpack> %block> diff --git a/cms/urls.py b/cms/urls.py index 7241242d5b59..aff79f5ca763 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -18,7 +18,7 @@ import openedx.core.djangoapps.lang_pref.views from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore import views as contentstore_views -from cms.djangoapps.contentstore.views.block import edit_view_xblock +from cms.djangoapps.contentstore.views.block import xblock_actions_view from cms.djangoapps.contentstore.views.organization import OrganizationListView from openedx.core.apidocs import api_info from openedx.core.djangoapps.password_policy import compliance as password_policy_compliance @@ -146,8 +146,8 @@ name='xblock_outline_handler'), re_path(fr'^xblock/container/{settings.USAGE_KEY_PATTERN}$', contentstore_views.xblock_container_handler, name='xblock_container_handler'), - re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}/editor$', edit_view_xblock, - name='xblock_editor_handler'), + re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}/actions/(?P[^/]+)$', xblock_actions_view, + name='xblock_actions_handler'), re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}/(?P[^/]+)$', contentstore_views.xblock_view_handler, name='xblock_view_handler'), re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}?$', contentstore_views.xblock_handler,