diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9d39d91c0..42156ede40 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,9 @@ Blades: Set initial video quality to large instead of default to avoid automatic Blades: Add an upload button for authors to provide students with an option to download a handout associated with a video (of arbitrary file format). BLD-1000. +Studio: Add "raw HTML" editor so that authors can write HTML that will not be +changed in any way. STUD-1562 + Blades: Show the HD button only if there is an HD version available. BLD-937. Studio: Add edit button to leaf xblocks on the container page. STUD-1306. diff --git a/cms/djangoapps/contentstore/features/component.feature b/cms/djangoapps/contentstore/features/component.feature index 90b8a8b843..482a070716 100644 --- a/cms/djangoapps/contentstore/features/component.feature +++ b/cms/djangoapps/contentstore/features/component.feature @@ -20,11 +20,13 @@ Feature: CMS.Component Adding | Text | | Announcement | | Zooming Image | + | Raw HTML | Then I see HTML components in this order: | Component | | Text | | Announcement | | Zooming Image | + | Raw HTML | Scenario: I can add Latex HTML components Given I am in Studio editing a new unit diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index e700c49df6..05598065b5 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -63,6 +63,8 @@ def see_a_multi_step_component(step, category): '
This template is similar to the Text template. The only difference is',
}
actual_html = world.css_html(selector, index=idx)
assert_in(html_matcher[step_hash['Component']], actual_html)
diff --git a/cms/djangoapps/contentstore/features/html-editor.feature b/cms/djangoapps/contentstore/features/html-editor.feature
index a23bc4650d..df44d06420 100644
--- a/cms/djangoapps/contentstore/features/html-editor.feature
+++ b/cms/djangoapps/contentstore/features/html-editor.feature
@@ -5,7 +5,7 @@ Feature: CMS.HTML Editor
Scenario: User can view metadata
Given I have created a Blank HTML Page
And I edit and select Settings
- Then I see only the HTML display name setting
+ Then I see the HTML component settings
# Safari doesn't save the name properly
@skip_safari
@@ -47,6 +47,26 @@ Feature: CMS.HTML Editor
-->
"""
+ Scenario: TinyMCE and CodeMirror preserve span tags
+ Given I have created a Blank HTML Page
+ When I edit the page
+ And type "Test" in the code editor and press OK
+ And I save the page
+ Then the page text contains:
+ """
+ Test
+ """
+
+ Scenario: TinyMCE and CodeMirror preserve math tags
+ Given I have created a Blank HTML Page
+ When I edit the page
+ And type "" in the code editor and press OK
+ And I save the page
+ Then the page text contains:
+ """
+
+ """
+
Scenario: TinyMCE toolbar buttons are as expected
Given I have created a Blank HTML Page
When I edit the page
@@ -57,7 +77,7 @@ Feature: CMS.HTML Editor
When I edit the page
And type "
" in the code editor and press OK
Then the src link is rewritten to "c4x/MITx/999/asset/image.jpg"
- And the code editor displays "


display as code
original visual text
' spyOn(@descriptor, 'getVisualEditor').andCallFake () -> visualEditorStub data = @descriptor.save().data - expect(data).toEqual('from visual editor') + expect(data).toEqual('raw text') it 'Performs link rewriting for static assets when saving', -> visualEditorStub = - isDirty: () -> true getContent: () -> 'from visual editor with /c4x/foo/bar/asset/image.jpg' spyOn(@descriptor, 'getVisualEditor').andCallFake () -> visualEditorStub - @descriptor.base_asset_url = '/c4x/foo/bar/asset/' data = @descriptor.save().data expect(data).toEqual('from visual editor with /static/image.jpg') it 'When showing visual editor links are rewritten to c4x format', -> - @descriptor = new HTMLEditingDescriptor($('.html-edit')) - @descriptor.base_asset_url = '/c4x/foo/bar/asset/' - visualEditorStub = content: 'text /static/image.jpg' startContent: 'text /static/image.jpg' @@ -45,3 +38,10 @@ describe 'HTMLEditingDescriptor', -> @descriptor.initInstanceCallback(visualEditorStub) expect(visualEditorStub.getContent()).toEqual('text /c4x/foo/bar/asset/image.jpg') + describe 'Raw HTML Editor', -> + beforeEach -> + loadFixtures 'html-editor-raw.html' + @descriptor = new HTMLEditingDescriptor($('.test-component')) + it 'Returns data from raw editor', -> + data = @descriptor.save().data + expect(data).toEqual('raw text') diff --git a/common/lib/xmodule/xmodule/js/src/html/edit.coffee b/common/lib/xmodule/xmodule/js/src/html/edit.coffee index 97e713dad5..e55ad3b787 100644 --- a/common/lib/xmodule/xmodule/js/src/html/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/html/edit.coffee @@ -1,61 +1,79 @@ class @HTMLEditingDescriptor constructor: (element) -> - @element = element; + @element = element @base_asset_url = @element.find("#editor-tab").data('base-asset-url') + @editor_choice = @element.find("#editor-tab").data('editor') if @base_asset_url == undefined @base_asset_url = null - # Create an array of all content CSS links to use in and pass to Tiny MCE. - # We create this dynamically in order to support hashed files from our Django pipeline. - # CSS files that are to be used by Tiny MCE should contain the string "tinymce" so - # they can be found by the search below. - # We filter for only those files that are "content" files (as opposed to "skin" files). - tiny_mce_css_links = [] - $("link[rel=stylesheet][href*='tinymce']").filter("[href*='content']").each -> - tiny_mce_css_links.push $(this).attr("href") - return - -# This is a workaround for the fact that tinyMCE's baseURL property is not getting correctly set on AWS -# instances (like sandbox). It is not necessary to explicitly set baseURL when running locally. - tinyMCE.baseURL = "#{baseUrl}/js/vendor/tinymce/js/tinymce" -# This is necessary for the LMS bulk e-mail acceptance test. In that particular scenario, -# tinyMCE incorrectly decides that the suffix should be "", which means it fails to load files. - tinyMCE.suffix = ".min" - @tiny_mce_textarea = $(".tiny-mce", @element).tinymce({ - script_url : "#{baseUrl}/js/vendor/tinymce/js/tinymce/tinymce.full.min.js", - theme : "modern", - skin: 'studio-tmce4', - schema: "html5", - # Necessary to preserve relative URLs to our images. - convert_urls : false, - content_css : tiny_mce_css_links.join(", "), - formats : { - # tinyMCE does block level for code by default - code: {inline: 'code'} - }, - # Disable visual aid on borderless table. - visual: false, - plugins: "textcolor, link, image, codemirror", - codemirror: { - path: "#{baseUrl}/js/vendor" - }, - image_advtab: true, - # We may want to add "styleselect" when we collect all styles used throughout the LMS - toolbar: "formatselect | fontselect | bold italic underline forecolor wrapAsCode | bullist numlist outdent indent blockquote | link unlink image | code", - block_formats: "Paragraph=p;Preformatted=pre;Heading 1=h1;Heading 2=h2;Heading 3=h3", - width: '100%', - height: '400px', - menubar: false, - statusbar: false, - # Necessary to avoid stripping of style tags. - valid_children : "+body[style]", - setup: @setupTinyMCE, - # Cannot get access to tinyMCE Editor instance (for focusing) until after it is rendered. - # The tinyMCE callback passes in the editor as a parameter. - init_instance_callback: @initInstanceCallback + # We always create the "raw editor" so we can get the text out of it if necessary on save. + @advanced_editor = CodeMirror.fromTextArea($(".edit-box", @element)[0], { + mode: "text/html" + lineNumbers: true + lineWrapping: true }) + if @editor_choice == 'visual' + @$advancedEditorWrapper = $(@advanced_editor.getWrapperElement()) + @$advancedEditorWrapper.addClass('is-inactive') + # Create an array of all content CSS links to use in and pass to Tiny MCE. + # We create this dynamically in order to support hashed files from our Django pipeline. + # CSS files that are to be used by Tiny MCE should contain the string "tinymce" so + # they can be found by the search below. + # We filter for only those files that are "content" files (as opposed to "skin" files). + tiny_mce_css_links = [] + $("link[rel=stylesheet][href*='tinymce']").filter("[href*='content']").each -> + tiny_mce_css_links.push $(this).attr("href") + return + + # This is a workaround for the fact that tinyMCE's baseURL property is not getting correctly set on AWS + # instances (like sandbox). It is not necessary to explicitly set baseURL when running locally. + tinyMCE.baseURL = "#{baseUrl}/js/vendor/tinymce/js/tinymce" + # This is necessary for the LMS bulk e-mail acceptance test. In that particular scenario, + # tinyMCE incorrectly decides that the suffix should be "", which means it fails to load files. + tinyMCE.suffix = ".min" + @tiny_mce_textarea = $(".tiny-mce", @element).tinymce({ + script_url : "#{baseUrl}/js/vendor/tinymce/js/tinymce/tinymce.full.min.js", + theme : "modern", + skin: 'studio-tmce4', + schema: "html5", + # Necessary to preserve relative URLs to our images. + convert_urls : false, + content_css : tiny_mce_css_links.join(", "), + formats : { + # tinyMCE does block level for code by default + code: {inline: 'code'} + }, + # Disable visual aid on borderless table. + visual: false, + plugins: "textcolor, link, image, codemirror", + codemirror: { + path: "#{baseUrl}/js/vendor" + }, + image_advtab: true, + # We may want to add "styleselect" when we collect all styles used throughout the LMS + toolbar: "formatselect | fontselect | bold italic underline forecolor wrapAsCode | bullist numlist outdent indent blockquote | link unlink image | code", + block_formats: "Paragraph=p;Preformatted=pre;Heading 1=h1;Heading 2=h2;Heading 3=h3", + width: '100%', + height: '400px', + menubar: false, + statusbar: false, + + # Necessary to avoid stripping of style tags. + valid_children : "+body[style]", + + # Allow any elements to be used, e.g. link, script, math + valid_elements: "*[*]", + extended_valid_elements: "*[*]", + invalid_elements: "", + + setup: @setupTinyMCE, + # Cannot get access to tinyMCE Editor instance (for focusing) until after it is rendered. + # The tinyMCE callback passes in the editor as a parameter. + init_instance_callback: @initInstanceCallback + }) + setupTinyMCE: (ed) => ed.addButton('wrapAsCode', { title : 'Code block', @@ -109,6 +127,9 @@ class @HTMLEditingDescriptor initInstanceCallback: (visualEditor) => visualEditor.setContent(rewriteStaticLinks(visualEditor.getContent({no_events: 1}), '/static/', @base_asset_url)) + # Unfortunately, just setting visualEditor.isNortDirty = true is not enough to convince TinyMCE we + # haven't dirtied the Editor. Store the raw content so we can compare it later. + @starting_content = visualEditor.getContent({format:"raw", no_events: 1}) visualEditor.focus() getVisualEditor: () -> @@ -120,6 +141,14 @@ class @HTMLEditingDescriptor return @visualEditor save: -> - visualEditor = @getVisualEditor() - text = rewriteStaticLinks(visualEditor.getContent({no_events: 1}), @base_asset_url, '/static/') + text = undefined + if @editor_choice == 'visual' + visualEditor = @getVisualEditor() + raw_content = visualEditor.getContent({format:"raw", no_events: 1}) + if @starting_content != raw_content + text = rewriteStaticLinks(visualEditor.getContent({no_events: 1}), @base_asset_url, '/static/') + + if text == undefined + text = @advanced_editor.getValue() + data: text diff --git a/common/lib/xmodule/xmodule/templates/html/raw.yaml b/common/lib/xmodule/xmodule/templates/html/raw.yaml new file mode 100644 index 0000000000..8d4c78ca89 --- /dev/null +++ b/common/lib/xmodule/xmodule/templates/html/raw.yaml @@ -0,0 +1,14 @@ +--- +metadata: + display_name: Raw HTML + editor: raw +data: | +This template is similar to the Text template. The only difference is + that this template opens in the Raw HTML editor rather than in the Visual + editor.
+ +The Raw HTML editor saves your HTML exactly as you enter it. + You can switch to the Visual editor by clicking the Settings tab and + changing the Editor setting to Visual. Note, however, that some of your + HTML may be modified when you save the component if you switch to the + Visual editor.
diff --git a/lms/templates/static_templates/server-error.html b/lms/templates/static_templates/server-error.html index cf5d79c7ce..6fce0c9a30 100644 --- a/lms/templates/static_templates/server-error.html +++ b/lms/templates/static_templates/server-error.html @@ -4,7 +4,7 @@
diff --git a/lms/templates/widgets/html-edit.html b/lms/templates/widgets/html-edit.html
index a3ad0ef7d3..8dcf6b67b8 100644
--- a/lms/templates/widgets/html-edit.html
+++ b/lms/templates/widgets/html-edit.html
@@ -1,7 +1,12 @@
<%! from django.utils.translation import ugettext as _ %>
-