From 606b3614023f7a55a8b3f516be00ba16f40ca9ad Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Wed, 15 Nov 2023 15:20:55 -0500 Subject: [PATCH 1/8] feat: Add option to copy/paste a unit from within the unit Also removes the last published section --- cms/static/js/base.js | 1 + cms/static/js/views/pages/container.js | 11 ++- .../js/views/pages/container_subviews.js | 47 ++++++++++- cms/static/js/views/utils/xblock_utils.js | 84 ++++++++++++++++++- cms/static/js/views/xblock.js | 4 + cms/static/sass/elements/_navigation.scss | 34 ++++++++ cms/templates/container.html | 61 ++++++++++++-- cms/templates/js/publish-history.underscore | 5 -- cms/templates/js/publish-xblock.underscore | 9 ++ lms/templates/seq_block.html | 25 ++---- 10 files changed, 247 insertions(+), 34 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index c8ab1a469145..5f970a89d592 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -75,6 +75,7 @@ function( $body.click(function() { $('.nav-dd .nav-item .wrapper-nav-sub').removeClass('is-shown'); $('.nav-dd .nav-item .title').removeClass('is-selected'); + $('.custom-dropdown .dropdown-options').hide(); }); $('.nav-dd .nav-item, .filterable-column .nav-item').click(function(e) { diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index e624021b47ef..b48e25fedc39 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -77,6 +77,7 @@ function($, _, Backbone, gettext, BasePage, model: this.model }); this.messageView.render(); + this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel"); // Display access message on units and split test components if (!this.isLibraryPage) { this.containerAccessView = new ContainerSubviews.ContainerAccess({ @@ -89,7 +90,8 @@ function($, _, Backbone, gettext, BasePage, el: this.$('#publish-unit'), model: this.model, // When "Discard Changes" is clicked, the whole page must be re-rendered. - renderPage: this.render + renderPage: this.render, + clipboardBroadcastChannel: this.clipboardBroadcastChannel, }); this.xblockPublisher.render(); @@ -120,7 +122,6 @@ function($, _, Backbone, gettext, BasePage, } this.listenTo(Backbone, 'move:onXBlockMoved', this.onXBlockMoved); - this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel"); }, getViewParameters: function() { @@ -144,6 +145,7 @@ function($, _, Backbone, gettext, BasePage, hiddenCss = 'is-hidden'; loadingElement.removeClass(hiddenCss); + self.initializePasteActionButton(); // Hide both blocks until we know which one to show xblockView.$el.addClass(hiddenCss); @@ -175,11 +177,16 @@ function($, _, Backbone, gettext, BasePage, if (!self.isLibraryPage && !self.isLibraryContentPage) { self.initializePasteButton(); } + }, block_added: options && options.block_added }); }, + initializePasteActionButton() { + // logic to hide/show paste button + }, + findXBlockElement: function(target) { return $(target).closest('.studio-xblock-wrapper'); }, diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js index b4ee286ae897..1e2c6d53b4fb 100644 --- a/cms/static/js/views/pages/container_subviews.js +++ b/cms/static/js/views/pages/container_subviews.js @@ -107,7 +107,8 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H events: { 'click .action-publish': 'publish', 'click .action-discard': 'discardChanges', - 'click .action-staff-lock': 'toggleStaffLock' + 'click .action-staff-lock': 'toggleStaffLock', + 'click .action-copy': 'copyToClipboard' }, // takes XBlockInfo as a model @@ -117,6 +118,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H this.template = this.loadTemplate('publish-xblock'); this.model.on('sync', this.onSync, this); this.renderPage = this.options.renderPage; + this.clipboardBroadcastChannel = this.options.clipboardBroadcastChannel; }, onSync: function(model) { @@ -174,6 +176,49 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H }); }, + copyToClipboard: function(e) { + e.preventDefault(); + const clipboardEndpoint = "/api/content-staging/v1/clipboard/"; + const usageKeyToCopy = this.model.get('id'); + // Start showing a "Copying" notification: + ViewUtils.runOperationShowingMessage(gettext('Copying'), () => { + return $.postJSON( + clipboardEndpoint, + { usage_key: usageKeyToCopy }, + ).then((data) => { + const status = data.content?.status; + if (status === "ready") { + // something that enables the paste button in the actions dropdown + this.clipboardBroadcastChannel.postMessage(data); + return data; + } else if (status === "loading") { + // The clipboard is being loaded asynchonously. + // Poll the endpoint until the copying process is complete: + const deferred = $.Deferred(); + const checkStatus = () => { + $.getJSON(clipboardEndpoint, (pollData) => { + const newStatus = pollData.content?.status; + if (newStatus === "ready") { + // something that enables the paste button in actions dropdown + this.clipboardBroadcastChannel.postMessage(pollData); + deferred.resolve(pollData); + } else if (newStatus === "loading") { + setTimeout(checkStatus, 1_000); + } else { + deferred.reject(); + throw new Error(`Unexpected clipboard status "${newStatus}" in successful API response.`); + } + }) + } + setTimeout(checkStatus, 1_000); + return deferred; + } else { + throw new Error(`Unexpected clipboard status "${status}" in successful API response.`); + } + }); + }); + }, + discardChanges: function(e) { var xblockInfo = this.model, renderPage = this.renderPage; diff --git a/cms/static/js/views/utils/xblock_utils.js b/cms/static/js/views/utils/xblock_utils.js index d3c1fce9e00e..9abe0866ed48 100644 --- a/cms/static/js/views/utils/xblock_utils.js +++ b/cms/static/js/views/utils/xblock_utils.js @@ -8,7 +8,7 @@ function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) { var addXBlock, duplicateXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState, getXBlockVisibilityClass, getXBlockListTypeClass, updateXBlockFields, getXBlockType, findXBlockInfo, - moveXBlock; + moveXBlock, pasteXBlock; /** * Represents the possible visibility states for an xblock: @@ -69,6 +69,85 @@ function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) { }); }; + pasteXBlock = function(target) { + var parentLocator = target.data('parent'), + displayName = target.data('default-name'); + + return ViewUtils.runOperationShowingMessage(gettext('Pasting'), () => { + return $.postJSON(ModuleUtils.getUpdateUrl(), { + parent_locator: parentLocator, + staged_content: "clipboard", + }).then((data) => { + return data; + }); + }).done((data) => { + const { + conflicting_files: conflictingFiles, + error_files: errorFiles, + new_files: newFiles, + } = data.static_file_notices; + + const notices = []; + if (errorFiles.length) { + notices.push((next) => new PromptView.Error({ + title: gettext("Some errors occurred"), + message: ( + gettext("The following required files could not be added to the course:") + + " " + errorFiles.join(", ") + ), + actions: {primary: {text: gettext("OK"), click: (x) => { x.hide(); next(); }}}, + })); + } + if (conflictingFiles.length) { + notices.push((next) => new PromptView.Warning({ + title: gettext("You may need to update a file(s) manually"), + message: ( + gettext( + "The following files already exist in this course but don't match the " + + "version used by the component you pasted:" + ) + " " + conflictingFiles.join(", ") + ), + actions: {primary: {text: gettext("OK"), click: (x) => { x.hide(); next(); }}}, + })); + } + if (newFiles.length) { + notices.push(() => new NotificationView.Info({ + title: gettext("New file(s) added to Files & Uploads."), + message: ( + gettext("The following required files were imported to this course:") + + " " + newFiles.join(", ") + ), + actions: { + primary: { + text: gettext('View files'), + click: function(notification) { + const article = document.querySelector('[data-course-assets]'); + const assetsUrl = $(article).attr('data-course-assets'); + window.location.href = assetsUrl; + return; + } + }, + secondary: { + text: gettext('Dismiss'), + click: function(notification) { + return notification.hide(); + } + } + } + })); + } + if (notices.length) { + // Show the notices, one at a time: + const showNext = () => { + const view = notices.shift()(showNext); + view.show(); + } + // Delay to avoid conflict with the "Pasting..." notification. + setTimeout(showNext, 1250); + } + }); + }; + /** * Duplicates the specified xblock element in its parent xblock. * @param {jquery Element} xblockElement The xblock element to be duplicated. @@ -308,6 +387,7 @@ function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) { getXBlockListTypeClass: getXBlockListTypeClass, updateXBlockFields: updateXBlockFields, getXBlockType: getXBlockType, - findXBlockInfo: findXBlockInfo + findXBlockInfo: findXBlockInfo, + pasteXBlock: pasteXBlock }; }); diff --git a/cms/static/js/views/xblock.js b/cms/static/js/views/xblock.js index 6b913d5239da..adedd2e2c093 100644 --- a/cms/static/js/views/xblock.js +++ b/cms/static/js/views/xblock.js @@ -14,6 +14,10 @@ function($, _, ViewUtils, BaseView, XBlock, HtmlUtils) { 'click .notification-action-button': 'fireNotificationActionEvent' }, + options: { + clipboardData: { content: null }, + }, + initialize: function() { BaseView.prototype.initialize.call(this); this.view = this.options.view; diff --git a/cms/static/sass/elements/_navigation.scss b/cms/static/sass/elements/_navigation.scss index 97ff8b8aacfb..fbc367206ac4 100644 --- a/cms/static/sass/elements/_navigation.scss +++ b/cms/static/sass/elements/_navigation.scss @@ -300,6 +300,40 @@ $seq-nav-height: 40px; @include border-right-style(solid); } + .custom-dropdown { + position: relative; + display: inline-block; + } + + .dropdown-options { + position: absolute; + top: 100%; + z-index: 1000; + background-color: #ffffff; + min-width: 265px; + right: 0; + + li { + padding: 0.5em 1em; + cursor: pointer; + + a { + display: block; + width: 100%; + color: black; + } + + .checkmark { + float: right; + margin-left: 10px; + } + } + } + + .dropdown-options li:hover { + background-color: #f1f1f1; + } + button { @extend %ui-fake-link; @extend %ui-clear-button; diff --git a/cms/templates/container.html b/cms/templates/container.html index 41fe2eb53781..2dfd47018097 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -53,6 +53,7 @@ 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"], function (XBlockInfo, XBlockView, XBlockUtils, ViewUtils) { var model = new XBlockInfo({ id: '${subsection.location|n, decode.utf8}' @@ -60,19 +61,63 @@ 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}' + 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() { - $('.seq_new_button').click(function(evt) { - evt.preventDefault(); - XBlockUtils.addXBlock($(evt.target)).done(function(locator) { - ViewUtils.redirect('/container/' + locator + '?action=new'); - return false; - }); - return false; + const optionIcons = { + 'New Unit': 'fa-plus', + 'Paste as new unit': 'fa-clipboard' + }; + + if (!this.options.clipboardData && !this.options.clipboardData.source_usage_key.includes("vertical")) { + $('.seq_paste_unit').hide(); + } + $('.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 + updateCheckmarks($(this).text().trim()); }); + updateCheckmarks = function(activeOptionText) { + $('.dropdown-options .checkmark').remove(); + $('.dropdown-options a').each(function() { + if ($(this).text().trim() === activeOptionText) { + $(this).append(''); // Checkmark icon + } + }); + } + + $('.dropdown-options').click(function(event) { + event.preventDefault(); + var $target = $(event.target), + newLabel = $target.text().trim(), + newIconClass = optionIcons[newLabel]; + + if ($target.hasClass('seq_new_unit') || $target.hasClass('seq_paste_unit')) { + // Change the text of the main button + $('.dropdown-toggle-button').text(newLabel); + // Change the icon of the main button + $('.dropdown-toggle-button').prepend(''); + + updateCheckmarks(newLabel); + + if ($target.hasClass('seq_new_unit')) { + XBlockUtils.addXBlock($(event.target)).done(function(locator) { + ViewUtils.redirect('/container/' + locator + '?action=new'); + }); + } + + if ($target.hasClass('seq_paste_unit')) { + XBlockUtils.pasteXBlock($(event.target)).done(function(data) { + ViewUtils.redirect('/container/' + data.locator + '?action=new'); + }); + } + } + }); }; + xblockView.render(); }); diff --git a/cms/templates/js/publish-history.underscore b/cms/templates/js/publish-history.underscore index ca8648503385..354faa55ee7e 100644 --- a/cms/templates/js/publish-history.underscore +++ b/cms/templates/js/publish-history.underscore @@ -10,8 +10,3 @@ if (published_on && published_by) { copy = gettext("Previously published"); } %> - -
- <% // xss-lint: disable=underscore-not-escaped %> -

<%= copy %>

-
diff --git a/cms/templates/js/publish-xblock.underscore b/cms/templates/js/publish-xblock.underscore index cc78ba256456..c1e61fd191cb 100644 --- a/cms/templates/js/publish-xblock.underscore +++ b/cms/templates/js/publish-xblock.underscore @@ -128,4 +128,13 @@ var visibleToStaffOnly = visibilityState === 'staff_only'; +
+ +
diff --git a/lms/templates/seq_block.html b/lms/templates/seq_block.html index 58cb2d2e7e65..89564bba5253 100644 --- a/lms/templates/seq_block.html +++ b/lms/templates/seq_block.html @@ -85,22 +85,15 @@ % endfor % endif % if exclude_units: -
  • - +
  • % endif From 9faf6f325ade3fa8a9601a4563e226e71c485852 Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Thu, 23 Nov 2023 20:36:11 -0500 Subject: [PATCH 2/8] fix: correct dropdown menu behaviour --- .../js/views/pages/container_subviews.js | 1 + cms/static/sass/elements/_navigation.scss | 18 +++- cms/static/sass/views/_container.scss | 9 ++ cms/templates/container.html | 96 ++++++++++++------- cms/templates/js/publish-xblock.underscore | 7 +- lms/templates/seq_block.html | 11 ++- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js index 1e2c6d53b4fb..96e860ab917b 100644 --- a/cms/static/js/views/pages/container_subviews.js +++ b/cms/static/js/views/pages/container_subviews.js @@ -178,6 +178,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H copyToClipboard: function(e) { e.preventDefault(); + e.stopPropagation(); const clipboardEndpoint = "/api/content-staging/v1/clipboard/"; const usageKeyToCopy = this.model.get('id'); // Start showing a "Copying" notification: diff --git a/cms/static/sass/elements/_navigation.scss b/cms/static/sass/elements/_navigation.scss index fbc367206ac4..453108c0e548 100644 --- a/cms/static/sass/elements/_navigation.scss +++ b/cms/static/sass/elements/_navigation.scss @@ -288,6 +288,11 @@ $seq-nav-height: 40px; ol { display: flex; + .custom-dropdown { + position: relative; + display: inline-flex; + } + li { box-sizing: border-box; min-width: 40px; @@ -300,9 +305,16 @@ $seq-nav-height: 40px; @include border-right-style(solid); } - .custom-dropdown { - position: relative; - display: inline-block; + .dropdown-main-button { + border-right: 1px solid #e7e7e7 !important; + } + + .dropdown-toggle-button { + width: 15% !important; + + &:hover { + border-bottom: 1px solid #e7e7e7 !important; + } } .dropdown-options { diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss index 2b55b00cb6f4..a77d7a69646f 100644 --- a/cms/static/sass/views/_container.scss +++ b/cms/static/sass/views/_container.scss @@ -239,6 +239,15 @@ color: $gray-l1; } } + + .action-copy { + width: 100%; + border-color: #0075b4; + + &:hover { + @extend %btn-primary-blue; + } + } } } diff --git a/cms/templates/container.html b/cms/templates/container.html index 2dfd47018097..18724724f002 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -66,56 +66,88 @@ }); xblockView.xblockReady = function() { - const optionIcons = { - 'New Unit': 'fa-plus', - 'Paste as new unit': 'fa-clipboard' + this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel"); + this.clipboardBroadcastChannel.onmessage = (event) => { + if (!event.data.source_usage_key?.includes("vertical")) { + $('.seq_paste_unit').closest('li').hide(); + } else { + $('.seq_paste_unit').closest('li').show(); + } + }; + var currentAction = "new_unit"; + var actionIcons = { + 'new_unit': 'fa fa-plus', + 'paste_unit': 'fa fa-clipboard' + }; + var actionLabels = { + 'new_unit': 'New Unit', + 'paste_unit': 'Paste Unit' }; - if (!this.options.clipboardData && !this.options.clipboardData.source_usage_key.includes("vertical")) { - $('.seq_paste_unit').hide(); + if (!this.options.clipboardData.content || !this.options.clipboardData.source_usage_key?.includes("vertical")) { + $('.seq_paste_unit').closest('li').hide(); } - $('.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 - updateCheckmarks($(this).text().trim()); - }); - updateCheckmarks = function(activeOptionText) { - $('.dropdown-options .checkmark').remove(); + var updateButtonAndCheckmarks = function(selectedAction) { + var iconName = actionIcons[selectedAction]; + var buttonIconSpan = $('.dropdown-main-button .icon'); + + // Update the button icon + buttonIconSpan.removeClass('fa-plus fa-clipboard').addClass(iconName); + $('.dropdown-options a').each(function() { - if ($(this).text().trim() === activeOptionText) { - $(this).append(''); // Checkmark icon + $(this).find('.checkmark').remove(); + if ($(this).data('action') === selectedAction) { + $(this).append(''); } }); - } + }; - $('.dropdown-options').click(function(event) { + $('.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); + }); + + $('.dropdown-options a').click(function(event) { event.preventDefault(); - var $target = $(event.target), - newLabel = $target.text().trim(), - newIconClass = optionIcons[newLabel]; + var $option = $(this); + var newAction = $option.data('action') - if ($target.hasClass('seq_new_unit') || $target.hasClass('seq_paste_unit')) { - // Change the text of the main button - $('.dropdown-toggle-button').text(newLabel); - // Change the icon of the main button - $('.dropdown-toggle-button').prepend(''); + var newLabel = actionLabels[newAction]; + $('.custom-dropdown .dropdown-main-button .button-label').text(newLabel); + currentAction = newAction; - updateCheckmarks(newLabel); + updateButtonAndCheckmarks(currentAction); + $('.custom-dropdown .dropdown-options').slideUp('fast'); + }); - if ($target.hasClass('seq_new_unit')) { - XBlockUtils.addXBlock($(event.target)).done(function(locator) { + $('#execute-action-button').on('click', function(event) { + event.preventDefault(); + var $button = $(this); + switch (currentAction) { + case 'new_unit': + XBlockUtils.addXBlock($button).done(function(locator) { ViewUtils.redirect('/container/' + locator + '?action=new'); }); - } - - if ($target.hasClass('seq_paste_unit')) { - XBlockUtils.pasteXBlock($(event.target)).done(function(data) { + break; + case 'paste_unit': + XBlockUtils.pasteXBlock($button).done(function(data) { ViewUtils.redirect('/container/' + data.locator + '?action=new'); }); - } + break; + default: + console.error('Unknown action'); + break; } }); + + if ($('.seq_paste_unit').is(':visible')) { + updateButtonAndCheckmarks(currentAction); + } else { + updateButtonAndCheckmarks('new_unit'); + } }; xblockView.render(); diff --git a/cms/templates/js/publish-xblock.underscore b/cms/templates/js/publish-xblock.underscore index c1e61fd191cb..66ef72fe8c81 100644 --- a/cms/templates/js/publish-xblock.underscore +++ b/cms/templates/js/publish-xblock.underscore @@ -131,9 +131,10 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
    diff --git a/lms/templates/seq_block.html b/lms/templates/seq_block.html index 89564bba5253..d0dbfb5ab4e5 100644 --- a/lms/templates/seq_block.html +++ b/lms/templates/seq_block.html @@ -86,13 +86,16 @@ % endif % if exclude_units: % endif From 61377cf6e60ef45b4fcd230019452041cb22dfef Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Thu, 23 Nov 2023 21:14:57 -0500 Subject: [PATCH 3/8] fix: remove last-published js tests --- .../views/pages/container_subviews_spec.js | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/cms/static/js/spec/views/pages/container_subviews_spec.js b/cms/static/js/spec/views/pages/container_subviews_spec.js index cde0b42d8109..28ea2a4b9196 100644 --- a/cms/static/js/spec/views/pages/container_subviews_spec.js +++ b/cms/static/js/spec/views/pages/container_subviews_spec.js @@ -581,33 +581,6 @@ describe('Container Subviews', function() { }); }); - describe('PublishHistory', function() { - var lastPublishCss = '.wrapper-last-publish'; - - it('renders never published when the block is unpublished', function() { - renderContainerPage(this, mockContainerXBlockHtml, { - published: false, published_on: null, published_by: null - }); - expect(containerPage.$(lastPublishCss).text()).toContain('Never published'); - }); - - it('renders the last published date and user when the block is published', function() { - renderContainerPage(this, mockContainerXBlockHtml); - fetch({ - published: true, published_on: 'Jul 01, 2014 at 12:45 UTC', published_by: 'amako' - }); - expect(containerPage.$(lastPublishCss).text()) - .toContain('Last published Jul 01, 2014 at 12:45 UTC by amako'); - }); - - it('renders correctly when the block is published without publish info', function() { - renderContainerPage(this, mockContainerXBlockHtml); - fetch({ - published: true, published_on: null, published_by: null - }); - expect(containerPage.$(lastPublishCss).text()).toContain('Previously published'); - }); - }); describe('Message Area', function() { var messageSelector = '.container-message .warning', From c9a1b87a3c1fc3b8eb354f32e83426c0828b8c83 Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Fri, 24 Nov 2023 10:03:03 -0500 Subject: [PATCH 4/8] fix: clean up unwanted code + styling changes --- cms/static/js/views/pages/container.js | 5 ----- cms/static/sass/views/_container.scss | 2 ++ lms/templates/seq_block.html | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index b48e25fedc39..9a15c779df1a 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -145,7 +145,6 @@ function($, _, Backbone, gettext, BasePage, hiddenCss = 'is-hidden'; loadingElement.removeClass(hiddenCss); - self.initializePasteActionButton(); // Hide both blocks until we know which one to show xblockView.$el.addClass(hiddenCss); @@ -183,10 +182,6 @@ function($, _, Backbone, gettext, BasePage, }); }, - initializePasteActionButton() { - // logic to hide/show paste button - }, - findXBlockElement: function(target) { return $(target).closest('.studio-xblock-wrapper'); }, diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss index a77d7a69646f..9f145d842efa 100644 --- a/cms/static/sass/views/_container.scss +++ b/cms/static/sass/views/_container.scss @@ -243,6 +243,8 @@ .action-copy { width: 100%; border-color: #0075b4; + padding-top: 12px; + padding-bottom: 12px; &:hover { @extend %btn-primary-blue; diff --git a/lms/templates/seq_block.html b/lms/templates/seq_block.html index d0dbfb5ab4e5..f568314c43b8 100644 --- a/lms/templates/seq_block.html +++ b/lms/templates/seq_block.html @@ -88,14 +88,14 @@ % endif From 4b55c46a0453ef26cd3cf45510c39d287e27d49e Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Fri, 24 Nov 2023 13:44:15 -0500 Subject: [PATCH 5/8] fix: increase xsslint limit --- scripts/xsslint_thresholds.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/xsslint_thresholds.json b/scripts/xsslint_thresholds.json index 6c35512f3398..26c267c074cd 100644 --- a/scripts/xsslint_thresholds.json +++ b/scripts/xsslint_thresholds.json @@ -11,7 +11,7 @@ "django-trans-missing-escape": 0, "javascript-concat-html": 2, "javascript-escape": 1, - "javascript-jquery-append": 1, + "javascript-jquery-append": 2, "javascript-jquery-html": 5, "javascript-jquery-insert-into-target": 2, "javascript-jquery-insertion": 0, @@ -36,5 +36,5 @@ "python-wrap-html": 0, "underscore-not-escaped": 2 }, - "total": 63 + "total": 64 } From 2e58e222a60f6c6cf0c740db5ca1f6c2a7d5eabd Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Sat, 25 Nov 2023 09:48:01 -0500 Subject: [PATCH 6/8] fix: reset action button when clipboard content changes --- cms/templates/container.html | 48 +++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/cms/templates/container.html b/cms/templates/container.html index 18724724f002..264c00118f79 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -54,7 +54,7 @@ } ); - require(["js/models/xblock_info", "js/views/xblock", "js/views/utils/xblock_utils", "common/js/components/utils/view_utils"], function (XBlockInfo, XBlockView, XBlockUtils, ViewUtils) { + 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}' }); @@ -66,28 +66,16 @@ }); xblockView.xblockReady = function() { - this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel"); - this.clipboardBroadcastChannel.onmessage = (event) => { - if (!event.data.source_usage_key?.includes("vertical")) { - $('.seq_paste_unit').closest('li').hide(); - } else { - $('.seq_paste_unit').closest('li').show(); - } - }; var currentAction = "new_unit"; var actionIcons = { 'new_unit': 'fa fa-plus', 'paste_unit': 'fa fa-clipboard' }; var actionLabels = { - 'new_unit': 'New Unit', - 'paste_unit': 'Paste Unit' + 'new_unit': gettext('New Unit'), + 'paste_unit': gettext('Paste Unit') }; - if (!this.options.clipboardData.content || !this.options.clipboardData.source_usage_key?.includes("vertical")) { - $('.seq_paste_unit').closest('li').hide(); - } - var updateButtonAndCheckmarks = function(selectedAction) { var iconName = actionIcons[selectedAction]; var buttonIconSpan = $('.dropdown-main-button .icon'); @@ -103,6 +91,36 @@ }); }; + var resetToDefaultAction = function() { + // Set the current action to "new unit" + currentAction = 'new_unit'; + + // Update the button label and icon to "new unit" + var defaultLabel = actionLabels[currentAction]; + var defaultIconName = actionIcons[currentAction]; + $('.dropdown-main-button .button-label').text(defaultLabel); + $('.dropdown-main-button .icon').removeClass('fa-plus fa-clipboard').addClass(defaultIconName); + + // Ensure that the checkmarks are updated to reflect the default state + updateButtonAndCheckmarks(currentAction); + + // Hide the "Paste as new unit" option + $('.seq_paste_unit').closest('li').hide(); + }; + + this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel"); + this.clipboardBroadcastChannel.onmessage = (event) => { + if (!event.data.source_usage_key?.includes("vertical")) { + resetToDefaultAction(); + } else { + $('.seq_paste_unit').closest('li').show(); + } + }; + + if (!this.options.clipboardData.content || !this.options.clipboardData.source_usage_key?.includes("vertical")) { + resetToDefaultAction(); + } + $('.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 From d2a08738a4aff069e7c6be786bd4d86c74dbbf50 Mon Sep 17 00:00:00 2001 From: Pooja Kulkarni Date: Thu, 30 Nov 2023 16:42:30 -0500 Subject: [PATCH 7/8] fix: change dropdown to regular menu --- cms/templates/container.html | 104 +++++++---------------------------- lms/templates/seq_block.html | 9 ++- 2 files changed, 24 insertions(+), 89 deletions(-) diff --git a/cms/templates/container.html b/cms/templates/container.html index 264c00118f79..cd9530ed5120 100644 --- a/cms/templates/container.html +++ b/cms/templates/container.html @@ -66,60 +66,27 @@ }); xblockView.xblockReady = function() { - var currentAction = "new_unit"; - var actionIcons = { - 'new_unit': 'fa fa-plus', - 'paste_unit': 'fa fa-clipboard' - }; - var actionLabels = { - 'new_unit': gettext('New Unit'), - 'paste_unit': gettext('Paste Unit') - }; - - var updateButtonAndCheckmarks = function(selectedAction) { - var iconName = actionIcons[selectedAction]; - var buttonIconSpan = $('.dropdown-main-button .icon'); - - // Update the button icon - buttonIconSpan.removeClass('fa-plus fa-clipboard').addClass(iconName); - - $('.dropdown-options a').each(function() { - $(this).find('.checkmark').remove(); - if ($(this).data('action') === selectedAction) { - $(this).append(''); - } - }); - }; - var resetToDefaultAction = function() { - // Set the current action to "new unit" - currentAction = 'new_unit'; - - // Update the button label and icon to "new unit" - var defaultLabel = actionLabels[currentAction]; - var defaultIconName = actionIcons[currentAction]; - $('.dropdown-main-button .button-label').text(defaultLabel); - $('.dropdown-main-button .icon').removeClass('fa-plus fa-clipboard').addClass(defaultIconName); - - // Ensure that the checkmarks are updated to reflect the default state - updateButtonAndCheckmarks(currentAction); - - // Hide the "Paste as new unit" option - $('.seq_paste_unit').closest('li').hide(); + 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) => { - if (!event.data.source_usage_key?.includes("vertical")) { - resetToDefaultAction(); - } else { - $('.seq_paste_unit').closest('li').show(); - } + toggleCaretButton(event.data); }; + toggleCaretButton(this.options.clipboardData); - if (!this.options.clipboardData.content || !this.options.clipboardData.source_usage_key?.includes("vertical")) { - resetToDefaultAction(); - } + $('#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 @@ -128,44 +95,13 @@ $(this).attr('aria-expanded', !isExpanded); }); - $('.dropdown-options a').click(function(event) { + $('.seq_paste_unit').on('click', function(event) { event.preventDefault(); - var $option = $(this); - var newAction = $option.data('action') - - var newLabel = actionLabels[newAction]; - $('.custom-dropdown .dropdown-main-button .button-label').text(newLabel); - currentAction = newAction; - - updateButtonAndCheckmarks(currentAction); - $('.custom-dropdown .dropdown-options').slideUp('fast'); - }); - - $('#execute-action-button').on('click', function(event) { - event.preventDefault(); - var $button = $(this); - switch (currentAction) { - case 'new_unit': - XBlockUtils.addXBlock($button).done(function(locator) { - ViewUtils.redirect('/container/' + locator + '?action=new'); - }); - break; - case 'paste_unit': - XBlockUtils.pasteXBlock($button).done(function(data) { - ViewUtils.redirect('/container/' + data.locator + '?action=new'); - }); - break; - default: - console.error('Unknown action'); - break; - } + $('.dropdown-options').hide(); + XBlockUtils.pasteXBlock($(this)).done(function(data) { + ViewUtils.redirect('/container/' + data.locator + '?action=new'); + }); }); - - if ($('.seq_paste_unit').is(':visible')) { - updateButtonAndCheckmarks(currentAction); - } else { - updateButtonAndCheckmarks('new_unit'); - } }; xblockView.render(); diff --git a/lms/templates/seq_block.html b/lms/templates/seq_block.html index f568314c43b8..3c8699bc8f18 100644 --- a/lms/templates/seq_block.html +++ b/lms/templates/seq_block.html @@ -86,16 +86,15 @@ % endif % if exclude_units: % endif From d18f98f1d4be477daa60ee81b10e7c27b862869c Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 30 Nov 2023 15:24:58 -0800 Subject: [PATCH 8/8] fix: small tweak to CSS to avoid button changing size --- cms/static/sass/views/_container.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss index 9f145d842efa..3fa41b571fbe 100644 --- a/cms/static/sass/views/_container.scss +++ b/cms/static/sass/views/_container.scss @@ -243,8 +243,10 @@ .action-copy { width: 100%; border-color: #0075b4; - padding-top: 12px; - padding-bottom: 12px; + padding-top: 10px; + padding-bottom: 10px; + line-height: 24px; + border-radius: 4px; &:hover { @extend %btn-primary-blue;