diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index ed75ccd25b8f..33bac6c67c7a 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -48,7 +48,8 @@ "add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu", "add-xblock-component-support-legend", "add-xblock-component-support-level", "add-xblock-component-menu-problem", "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history", - "unit-outline", "container-message", "container-access", "license-selector", + "unit-outline", "container-message", "container-access", "license-selector", "copy-clipboard-button", + "edit-title-button", ] diff --git a/cms/static/js/spec/views/modals/edit_xblock_spec.js b/cms/static/js/spec/views/modals/edit_xblock_spec.js index 8a28acab5bcb..e5331e30e486 100644 --- a/cms/static/js/spec/views/modals/edit_xblock_spec.js +++ b/cms/static/js/spec/views/modals/edit_xblock_spec.js @@ -61,7 +61,7 @@ describe('EditXBlockModal', function() { it('shows the correct title', function() { var requests = AjaxHelpers.requests(this); modal = showModal(requests, mockXBlockEditorHtml); - expect(modal.$('.modal-window-title').text()).toBe('Editing: Component'); + expect(modal.$('.modal-window-title span.modal-button-title').text()).toBe('Editing: Component'); }); it('does not show any editor mode buttons', function() { @@ -134,7 +134,7 @@ describe('EditXBlockModal', function() { it('shows the correct title', function() { var requests = AjaxHelpers.requests(this); modal = showModal(requests, mockXModuleEditorHtml); - expect(modal.$('.modal-window-title').text()).toBe('Editing: Component'); + expect(modal.$('.modal-window-title span.modal-button-title').text()).toBe('Editing: Component'); }); it('shows the correct default buttons', function() { diff --git a/cms/static/js/spec_helpers/edit_helpers.js b/cms/static/js/spec_helpers/edit_helpers.js index e46354862ea7..a9cc33445af4 100644 --- a/cms/static/js/spec_helpers/edit_helpers.js +++ b/cms/static/js/spec_helpers/edit_helpers.js @@ -90,6 +90,7 @@ installEditTemplates = function(append) { // Add templates needed by the edit XBlock modal TemplateHelpers.installTemplate('edit-xblock-modal'); TemplateHelpers.installTemplate('editor-mode-button'); + TemplateHelpers.installTemplate('edit-title-button'); // Add templates needed by the settings editor TemplateHelpers.installTemplate('metadata-editor'); diff --git a/cms/static/js/spec_helpers/modal_helpers.js b/cms/static/js/spec_helpers/modal_helpers.js index 3b6c154cb375..89b9b6b066ba 100644 --- a/cms/static/js/spec_helpers/modal_helpers.js +++ b/cms/static/js/spec_helpers/modal_helpers.js @@ -29,7 +29,7 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'common/js/spec_hel getModalTitle = function(modal) { var modalElement = getModalElement(modal); - return modalElement.find('.modal-window-title').text(); + return modalElement.find('.modal-window-title span.modal-button-title').text(); }; isShowingModal = function(modal) { diff --git a/cms/static/js/views/modals/base_modal.js b/cms/static/js/views/modals/base_modal.js index 24f703a20261..65b7f06ae021 100644 --- a/cms/static/js/views/modals/base_modal.js +++ b/cms/static/js/views/modals/base_modal.js @@ -63,6 +63,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'], }, render: function() { + // xss-lint: disable=javascript-jquery-html this.$el.html(this.modalTemplate({ name: this.options.modalName, type: this.options.modalType, @@ -83,6 +84,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'], renderContents: function() { var contentHtml = this.getContentHtml(); + // xss-lint: disable=javascript-jquery-html this.$('.modal-content').html(contentHtml); }, @@ -146,6 +148,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'], name: name, isPrimary: isPrimary }); + // xss-lint: disable=javascript-jquery-append this.getActionBar().find('ul').append(html); }, @@ -178,8 +181,8 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'], modalWindow = this.$el.find(this.options.modalWindowClass); availableWidth = $(window).width(); availableHeight = $(window).height(); - maxWidth = availableWidth * 0.80; - maxHeight = availableHeight * 0.80; + maxWidth = availableWidth * 0.98; + maxHeight = availableHeight * 0.98; modalWidth = Math.min(modalWindow.outerWidth(), maxWidth); modalHeight = Math.min(modalWindow.outerHeight(), maxHeight); diff --git a/cms/static/js/views/modals/edit_xblock.js b/cms/static/js/views/modals/edit_xblock.js index af3ead91e619..661a3c090c84 100644 --- a/cms/static/js/views/modals/edit_xblock.js +++ b/cms/static/js/views/modals/edit_xblock.js @@ -11,7 +11,8 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod var EditXBlockModal = BaseModal.extend({ events: _.extend({}, BaseModal.prototype.events, { 'click .action-save': 'save', - 'click .action-modes a': 'changeMode' + 'click .action-modes a': 'changeMode', + 'click .title-edit-button': 'clickTitleButton' }), options: $.extend({}, BaseModal.prototype.options, { @@ -40,6 +41,7 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod this.xblockInfo = XBlockViewUtils.findXBlockInfo(xblockElement, rootXBlockInfo); this.options.modalType = this.xblockInfo.get('category'); this.editOptions = options; + this.render(); this.show(); @@ -68,6 +70,11 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod }); }, + createTitleEditor: function(title) { + // xss-lint: disable=javascript-jquery-html + this.$('.modal-window-title').html(this.loadTemplate('edit-title-button')({title: title})); + }, + onDisplayXBlock: function() { var editorView = this.editorView, title = this.getTitle(), @@ -84,7 +91,7 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod // Update the custom editor's title editorView.$('.component-name').text(title); } else { - this.$('.modal-window-title').text(title); + this.createTitleEditor(title); if (editorView.getDataEditor() && editorView.getMetadataEditor()) { this.addDefaultModes(); // If the plugins content element exists, add a button to reveal it. @@ -103,8 +110,6 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod } this.getActionBar().show(); } - - // Resize the modal to fit the window this.resize(); }, @@ -146,7 +151,6 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod }, changeMode: function(event) { - this.removeCheatsheetVisibility(); var $parent = $(event.target.parentElement), mode = $parent.data('mode'); event.preventDefault(); @@ -207,16 +211,30 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_mod })); }, - removeCheatsheetVisibility: function() { - var $cheatsheet = $('article.simple-editor-open-ended-cheatsheet'); - if ($cheatsheet.length === 0) { - $cheatsheet = $('article.simple-editor-cheatsheet'); - } - if ($cheatsheet.hasClass('shown')) { - $cheatsheet.removeClass('shown'); - $('.modal-content').removeClass('cheatsheet-is-shown'); - } + clickTitleButton: function(event) { + var self = this, + oldTitle = this.xblockInfo.get('display_name'), + titleElt = this.$('.modal-window-title'), + $input = $(''), + 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; diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 1fc77092ba79..fd4a05d10f69 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -30,6 +30,7 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/pages/base_page view: 'container_preview', + defaultViewClass: ContainerView, // Overridable by subclasses-- determines whether the XBlock component diff --git a/cms/static/sass/elements/_modal-window.scss b/cms/static/sass/elements/_modal-window.scss index 4295148d025a..25f655257421 100644 --- a/cms/static/sass/elements/_modal-window.scss +++ b/cms/static/sass/elements/_modal-window.scss @@ -219,6 +219,11 @@ color: $blue-d4; } } + .clipboard-button { + position: absolute; + right: 30px; + bottom: 30px; + } } } @@ -248,7 +253,7 @@ // large modals - component editors and interactives // ------------------------ .modal-lg { - width: 70%; + width: 95%; min-width: ($baseline*27.5); height: auto; @@ -266,7 +271,7 @@ } .editor-modes { - width: 48%; + width: 49%; display: inline-block; @include text-align(right); @@ -378,7 +383,6 @@ } } - // MODAL TYPE: component - video modal (includes special overrides for xblock-related editing view) .modal-lg.modal-type-video { .modal-content { diff --git a/cms/templates/js/copy-clipboard-button.underscore b/cms/templates/js/copy-clipboard-button.underscore new file mode 100644 index 000000000000..6ba471365196 --- /dev/null +++ b/cms/templates/js/copy-clipboard-button.underscore @@ -0,0 +1,5 @@ + + +<%- gettext('Copy Component Location') %> + + \ No newline at end of file diff --git a/cms/templates/js/edit-title-button.underscore b/cms/templates/js/edit-title-button.underscore new file mode 100644 index 000000000000..87fcb1e16c2c --- /dev/null +++ b/cms/templates/js/edit-title-button.underscore @@ -0,0 +1 @@ +<%- title %> diff --git a/cms/templates/js/metadata-editor.underscore b/cms/templates/js/metadata-editor.underscore index da4e32fc73ff..67898a77b63b 100644 --- a/cms/templates/js/metadata-editor.underscore +++ b/cms/templates/js/metadata-editor.underscore @@ -3,10 +3,4 @@
  • <% }) %> -
  • -
    - - <%- locator %> -
    -
  • diff --git a/cms/templates/widgets/problem-edit.html b/cms/templates/widgets/problem-edit.html index da0be1724eb3..42643c0ba093 100644 --- a/cms/templates/widgets/problem-edit.html +++ b/cms/templates/widgets/problem-edit.html @@ -1,4 +1,5 @@ <%! from django.utils.translation import ugettext as _ %> +<%page expression_filter="h"/> <%namespace name='static' file='../static_content.html'/> <% isLaTexProblem='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] and enable_latex_compiler %> @@ -18,6 +19,7 @@ ${_( + ${_("Heading")}
  • @@ -25,6 +27,7 @@ ${_( + ${_("Multiple Choice")}
  • @@ -32,6 +35,7 @@ ${_( + ${_("Checkboxes")}
  • @@ -39,6 +43,7 @@ ${_( + ${_("Text Input")}
  • @@ -46,6 +51,7 @@ ${_( + ${_("Numerical Input")}
  • @@ -53,6 +59,7 @@ ${_( + ${_("Dropdown")}
  • @@ -60,103 +67,108 @@ ${_( + ${_("Explanation")}
  • - - %endif - - - - - <%include file="metadata-edit.html" /> diff --git a/common/lib/xmodule/xmodule/css/problem/edit.scss b/common/lib/xmodule/xmodule/css/problem/edit.scss index e0a43c5fb2e9..9f43db39e00a 100644 --- a/common/lib/xmodule/xmodule/css/problem/edit.scss +++ b/common/lib/xmodule/xmodule/css/problem/edit.scss @@ -23,50 +23,37 @@ } } - .cheatsheet-toggle { - width: 21px; - height: 21px; - padding: 0; - margin: -1px 5px 0 15px; - border-radius: 22px; - border: 1px solid #a5aaaf; - background: #e5ecf3; - font-size: 13px; - font-weight: 700; - color: #565d64; - text-align: center; - } } } .simple-editor-cheatsheet { position: absolute; - top: 0; - left: 100%; + top: 41px; + left: 70%; width: 0; - border-radius: 0 3px 3px 0; - - @include linear-gradient(left, $shadow-l1, $transparent 4px); + border-left: 1px solid $gray-l2; - background-color: $white; + background-color: $lightGrey; overflow: hidden; - @include transition(width 0.3s linear 0s); - &.shown { - width: 20%; - height: 100%; + width: 30%; + height: 92%; overflow-y: scroll; } .cheatsheet-wrapper { - padding: 10%; + padding: 5%; } h6 { + margin-top: 4px; margin-bottom: 7px; + margin-left: 4px; font-size: 15px; font-weight: 700; + display: inline-block; + vertical-align: top; } .row { @@ -86,7 +73,6 @@ display: block; &.sample { - width: 60px; margin-right: 30px; .icon { @@ -110,6 +96,7 @@ // adding padding to simple editor only - adjacent selector is needed since there are no toggles for CodeMirror .markdown-box + .CodeMirror { padding: 10px; + width: 69%; } } diff --git a/common/lib/xmodule/xmodule/js/src/problem/edit.js b/common/lib/xmodule/xmodule/js/src/problem/edit.js index f3937427459d..c1f26ef9dc6c 100644 --- a/common/lib/xmodule/xmodule/js/src/problem/edit.js +++ b/common/lib/xmodule/xmodule/js/src/problem/edit.js @@ -53,12 +53,6 @@ function MarkdownEditingDescriptor(element) { var that = this; - this.toggleCheatsheetVisibility = function() { - return MarkdownEditingDescriptor.prototype.toggleCheatsheetVisibility.apply(that, arguments); - }; - this.toggleCheatsheet = function() { - return MarkdownEditingDescriptor.prototype.toggleCheatsheet.apply(that, arguments); - }; this.onToolbarButton = function() { return MarkdownEditingDescriptor.prototype.onToolbarButton.apply(that, arguments); }; @@ -75,7 +69,6 @@ // Add listeners for toolbar buttons (only present for markdown editor) this.element.on('click', '.xml-tab', this.onShowXMLButton); this.element.on('click', '.format-buttons button', this.onToolbarButton); - this.element.on('click', '.cheatsheet-toggle', this.toggleCheatsheet); // Hide the XML text area $(this.element.find('.xml-box')).hide(); } else { @@ -110,10 +103,6 @@ */ MarkdownEditingDescriptor.prototype.onShowXMLButton = function(e) { e.preventDefault(); - if (this.cheatsheet && this.cheatsheet.hasClass('shown')) { - this.cheatsheet.toggleClass('shown'); - this.toggleCheatsheetVisibility(); - } if (this.confirmConversionToXml()) { this.createXMLEditor(MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue())); this.xml_editor.setCursor(0); @@ -169,29 +158,6 @@ } }; - /* - Event listener for toggling cheatsheet (only possible when markdown editor is visible). - */ - MarkdownEditingDescriptor.prototype.toggleCheatsheet = function(e) { - var that = this; - e.preventDefault(); - if (!$(this.markdown_editor.getWrapperElement()).find('.simple-editor-cheatsheet')[0]) { - this.cheatsheet = $($('#simple-editor-cheatsheet').html()); - $(this.markdown_editor.getWrapperElement()).append(this.cheatsheet); - } - this.toggleCheatsheetVisibility(); - return setTimeout((function() { - return that.cheatsheet.toggleClass('shown'); - }), 10); - }; - - /* - Function to toggle cheatsheet visibility. - */ - MarkdownEditingDescriptor.prototype.toggleCheatsheetVisibility = function() { - return $('.modal-content').toggleClass('cheatsheet-is-shown'); - }; - /* Stores the current editor and hides the one that is not displayed. */ @@ -212,7 +178,6 @@ MarkdownEditingDescriptor.prototype.save = function() { this.element.off('click', '.xml-tab', this.changeEditor); this.element.off('click', '.format-buttons button', this.onToolbarButton); - this.element.off('click', '.cheatsheet-toggle', this.toggleCheatsheet); if (this.current_editor === this.markdown_editor) { return { data: MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()), @@ -327,12 +292,13 @@ // description xml = xml.replace(/>>([^]+?)<\n'; + label = '\n'; // xss-lint: disable=javascript-concat-html // don't add empty tag if (result.length === 1 || !result[1]) { return label; } + // xss-lint: disable=javascript-concat-html return label + '' + result[1] + '\n'; }); @@ -425,6 +391,7 @@ optiontag += correct[1]; } optiontag += '">'; + // xss-lint: disable=javascript-concat-html return '\n\n' + optiontag + '\n\n\n'; } @@ -442,12 +409,15 @@ if (label) { label = ' label="' + label + '"'; } + // xss-lint: disable=javascript-concat-html hintstr = ' ' + textHint.hint + ''; } + // xss-lint: disable=javascript-concat-html optionlines += ' ' + textHint.nothint + hintstr + '\n'; } } + // xss-lint: disable=javascript-concat-html return '\n\n \n' + optionlines + ' \n\n\n'; }); @@ -477,8 +447,10 @@ hint = extractHint(value); if (hint.hint) { value = hint.nothint; + // xss-lint: disable=javascript-concat-html value = value + ' ' + hint.hint + ''; } + // xss-lint: disable=javascript-concat-html choices += ' ' + value + '\n'; } } @@ -515,6 +487,7 @@ // lone case of hint text processing outside of extractHint, since syntax here is unique hintbody = abhint[2]; hintbody = hintbody.replace('&lf;', '\n').trim(); + // xss-lint: disable=javascript-concat-html endHints += ' ' + hintbody + '\n'; continue; // bail @@ -534,11 +507,13 @@ // checkbox choicehints get their own line, since there can be two of them // You’re right that apple is a fruit. if (select) { + // xss-lint: disable=javascript-concat-html hints += '\n ' + select[2].trim() + ''; } select = /{\s*(u|unselected):((.|\n)*?)}/i.exec(inner); if (select) { + // xss-lint: disable=javascript-concat-html hints += '\n ' + select[2].trim() + ''; } @@ -549,6 +524,7 @@ value = hint.nothint; } } + // xss-lint: disable=javascript-concat-html groupString += ' ' + value + hints + '\n'; } } @@ -694,10 +670,12 @@ typ = ' type="ci regexp"'; firstAnswer = firstAnswer.slice(1).trim(); } + // xss-lint: disable=javascript-concat-html string = '\n'; if (textHint.hint) { + // xss-lint: disable=javascript-concat-html string += ' ' + - textHint.hint + '\n'; + textHint.hint + '\n'; // xss-lint: disable=javascript-concat-html } // Subsequent cases are not= or or= @@ -705,16 +683,22 @@ textHint = extractHint(values[i]); notMatch = /^not\=\s*(.*)/.exec(textHint.nothint); if (notMatch) { + // xss-lint: disable=javascript-concat-html string += ' ' + textHint.hint + '\n'; + continue; } orMatch = /^or\=\s*(.*)/.exec(textHint.nothint); if (orMatch) { // additional_answer with answer= attribute + // xss-lint: disable=javascript-concat-html string += ' '; if (textHint.hint) { + // xss-lint: disable=javascript-concat-html string += '' + + // xss-lint: disable=javascript-concat-html textHint.hint + ''; } string += '\n'; @@ -732,12 +716,15 @@ // replace explanations xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) { + // xss-lint: disable=javascript-concat-html return '\n
    \n' + + // xss-lint: disable=javascript-concat-html gettext('Explanation') + '\n\n' + p1 + '\n
    \n
    '; }); // replace code blocks xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) { + // xss-lint: disable=javascript-concat-html return '
    ' + p1 + '
    '; }); diff --git a/common/test/acceptance/pages/studio/problem_editor.py b/common/test/acceptance/pages/studio/problem_editor.py index c9e4634b97b0..81d7338e3d8b 100644 --- a/common/test/acceptance/pages/studio/problem_editor.py +++ b/common/test/acceptance/pages/studio/problem_editor.py @@ -28,7 +28,7 @@ def set_field_val(self, field_display_name, field_value): """ If editing, set the value of a field. """ - selector = u'.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name) + selector = u'.metadata_edit li.field label:contains("{}") + input'.format(field_display_name) script = "$(arguments[0]).val(arguments[1]).change();" self.browser.execute_script(script, selector, field_value)