From 464446331275b231eb1ddc59dc816cb46f7155c6 Mon Sep 17 00:00:00 2001 From: Glib Glugovskiy Date: Fri, 26 Apr 2024 10:24:56 +0300 Subject: [PATCH 1/4] feat: add move option to a modal window --- cms/djangoapps/contentstore/views/block.py | 14 +++++++++++--- cms/templates/container_editor.html | 12 +++++++++++- cms/urls.py | 6 +++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/cms/djangoapps/contentstore/views/block.py b/cms/djangoapps/contentstore/views/block.py index de70e8ce5fe6..0623151d1a10 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 @@ -72,8 +73,10 @@ CREATE_IF_NOT_FOUND = ["course_info"] # Useful constants for defining predicates -NEVER = lambda x: False -ALWAYS = lambda x: True +def NEVER(x): + return False +def ALWAYS(x): + return True # Disable atomic requests so transactions made during the request commit immediately instead of waiting for the end of @@ -301,20 +304,25 @@ 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. """ 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/templates/container_editor.html b/cms/templates/container_editor.html index a5afa5125e62..c43332649434 100644 --- a/cms/templates/container_editor.html +++ b/cms/templates/container_editor.html @@ -164,6 +164,12 @@ ${_("Edit")} + @@ -172,7 +178,11 @@ - - - - @@ -260,64 +188,24 @@ }); <%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); - }); - - $('.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'); - }); - }); - }; - - xblockView.render(); + 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) { + if ('${action_name|n, decode.utf8}' === 'move') { + var parentModel = new XBlockInfo({ id: '${unit.location|n, decode.utf8}', category: 'vertical' }); + var moveModal = new MoveXBlockModal({ + sourceXBlockInfo: new XBlockInfo(${xblock_info | n, dump_js_escaped_json}), + sourceParentXBlockInfo: parentModel, + XBlockURLRoot: "/xblock", + outlineURL: '${outline_url | n, js_escaped_string}', + }); + moveModal.show(); + } else if ('${action_name|n, decode.utf8}' === 'edit') { + var editModal = new EditXBlockModal(); + + editModal.edit([], new XBlockInfo(${xblock_info | n, dump_js_escaped_json}), {}); + } }); From 912a0365e31116b4b2ebdc0f2de501e756ca2dd2 Mon Sep 17 00:00:00 2001 From: PKulkoRaccoonGang Date: Fri, 3 May 2024 19:42:26 +0300 Subject: [PATCH 3/4] refactor: refactored iframe post msg logic --- cms/djangoapps/contentstore/views/block.py | 6 ++-- .../js/views/modals/move_xblock_modal.js | 4 --- .../js/views/utils/move_xblock_utils.js | 25 ++++++++++--- cms/templates/container_editor.html | 36 +++++++++++-------- cms/urls.py | 2 +- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/cms/djangoapps/contentstore/views/block.py b/cms/djangoapps/contentstore/views/block.py index 0623151d1a10..58d0c048f12e 100644 --- a/cms/djangoapps/contentstore/views/block.py +++ b/cms/djangoapps/contentstore/views/block.py @@ -73,10 +73,8 @@ CREATE_IF_NOT_FOUND = ["course_info"] # Useful constants for defining predicates -def NEVER(x): - return False -def ALWAYS(x): - return True +NEVER = lambda x: False +ALWAYS = lambda x: True # Disable atomic requests so transactions made during the request commit immediately instead of waiting for the end of diff --git a/cms/static/js/views/modals/move_xblock_modal.js b/cms/static/js/views/modals/move_xblock_modal.js index 872a21eae05d..8b3abad0703b 100644 --- a/cms/static/js/views/modals/move_xblock_modal.js +++ b/cms/static/js/views/modals/move_xblock_modal.js @@ -185,10 +185,6 @@ function($, Backbone, _, gettext, BaseView, XBlockViewUtils, MoveXBlockUtils, Ht targetParentLocator: this.targetParentXBlockInfo.id } ); - window.parent.postMessage({ - method: 'close_edit_modal', - msg: 'Sends a message when the modal window is closed' - }, '*'); } }); diff --git a/cms/static/js/views/utils/move_xblock_utils.js b/cms/static/js/views/utils/move_xblock_utils.js index 9bde9c6d087e..178185fe6b73 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_edit_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 a06e4565a1c6..43f23d2100a4 100644 --- a/cms/templates/container_editor.html +++ b/cms/templates/container_editor.html @@ -192,20 +192,28 @@ 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) { - if ('${action_name|n, decode.utf8}' === 'move') { - var parentModel = new XBlockInfo({ id: '${unit.location|n, decode.utf8}', category: 'vertical' }); - var moveModal = new MoveXBlockModal({ - sourceXBlockInfo: new XBlockInfo(${xblock_info | n, dump_js_escaped_json}), - sourceParentXBlockInfo: parentModel, - XBlockURLRoot: "/xblock", - outlineURL: '${outline_url | n, js_escaped_string}', - }); - moveModal.show(); - } else if ('${action_name|n, decode.utf8}' === 'edit') { - var editModal = new EditXBlockModal(); - - editModal.edit([], new XBlockInfo(${xblock_info | n, dump_js_escaped_json}), {}); - } + 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(); + } + + function showEditModal(xblockInfo) { + var editModal = new EditXBlockModal(); + editModal.edit([], new XBlockInfo(xblockInfo), {}); + } + + 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}); + } }); diff --git a/cms/urls.py b/cms/urls.py index b5077073ad30..aff79f5ca763 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -146,7 +146,7 @@ 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}/actions/(?P[^/]+)$$', xblock_actions_view, + 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'), From 2a1aa3474d26d4eeb875717120e2c258efb637e7 Mon Sep 17 00:00:00 2001 From: PKulkoRaccoonGang Date: Tue, 7 May 2024 13:38:49 +0300 Subject: [PATCH 4/4] refactor: after review --- cms/djangoapps/contentstore/views/block.py | 4 +++- cms/static/js/views/modals/base_modal.js | 2 +- cms/static/js/views/modals/edit_xblock.js | 4 ++-- cms/static/js/views/utils/move_xblock_utils.js | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cms/djangoapps/contentstore/views/block.py b/cms/djangoapps/contentstore/views/block.py index 58d0c048f12e..8104b09ada70 100644 --- a/cms/djangoapps/contentstore/views/block.py +++ b/cms/djangoapps/contentstore/views/block.py @@ -307,7 +307,9 @@ def xblock_view_handler(request, usage_key_string, view_name): @login_required 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): diff --git a/cms/static/js/views/modals/base_modal.js b/cms/static/js/views/modals/base_modal.js index 4d4ecc99f401..828289533a73 100644 --- a/cms/static/js/views/modals/base_modal.js +++ b/cms/static/js/views/modals/base_modal.js @@ -120,7 +120,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'], event.stopPropagation(); // Make sure parent modals don't see the click } window.parent.postMessage({ - method: 'close_edit_modal', + 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 5ab9809b6a9d..0ced05cea737 100644 --- a/cms/static/js/views/modals/edit_xblock.js +++ b/cms/static/js/views/modals/edit_xblock.js @@ -209,8 +209,8 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE Backbone.trigger('xblock:editorModalHidden'); window.parent.postMessage({ - method: 'close_edit_modal', - msg: 'Sends a message when the modal window is closed' + method: 'close_modal', + msg: 'Sends a message when the edit modal window is closed' }, '*'); BaseModal.prototype.hide.call(this); diff --git a/cms/static/js/views/utils/move_xblock_utils.js b/cms/static/js/views/utils/move_xblock_utils.js index 178185fe6b73..f3bcc675f1da 100644 --- a/cms/static/js/views/utils/move_xblock_utils.js +++ b/cms/static/js/views/utils/move_xblock_utils.js @@ -41,7 +41,7 @@ function($, _, Backbone, Feedback, AlertView, XBlockViewUtils, MoveXBlockUtils, } }, '*'); window.parent.postMessage({ - method: 'close_edit_modal', + method: 'close_modal', msg: 'Sends a message when the modal window is closed' }, '*');