Start working on COE editor, fix name checking in module_render
This commit is contained in:
@@ -221,3 +221,9 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
|
||||
has_score = True
|
||||
always_recalculate_grades = True
|
||||
template_dir_name = "combinedopenended"
|
||||
|
||||
#Specify whether or not to pass in S3 interface
|
||||
needs_s3_interface = True
|
||||
|
||||
#Specify whether or not to pass in open ended interface
|
||||
needs_open_ended_interface = True
|
||||
|
||||
299
common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
Normal file
299
common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
Normal file
@@ -0,0 +1,299 @@
|
||||
class @MarkdownEditingDescriptor extends XModule.Descriptor
|
||||
# TODO really, these templates should come from or also feed the cheatsheet
|
||||
@multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n"
|
||||
@checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n"
|
||||
@stringInputTemplate: "= answer\n"
|
||||
@numberInputTemplate: "= answer +- x%\n"
|
||||
@selectTemplate: "[[incorrect, (correct), incorrect]]\n"
|
||||
@headerTemplate: "Header\n=====\n"
|
||||
@explanationTemplate: "[explanation]\nShort explanation\n[explanation]\n"
|
||||
|
||||
constructor: (element) ->
|
||||
@element = element
|
||||
|
||||
if $(".markdown-box", @element).length != 0
|
||||
@markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], {
|
||||
lineWrapping: true
|
||||
mode: null
|
||||
})
|
||||
@setCurrentEditor(@markdown_editor)
|
||||
# Add listeners for toolbar buttons (only present for markdown editor)
|
||||
@element.on('click', '.xml-tab', @onShowXMLButton)
|
||||
@element.on('click', '.format-buttons a', @onToolbarButton)
|
||||
@element.on('click', '.cheatsheet-toggle', @toggleCheatsheet)
|
||||
# Hide the XML text area
|
||||
$(@element.find('.xml-box')).hide()
|
||||
else
|
||||
@createXMLEditor()
|
||||
|
||||
###
|
||||
Creates the XML Editor and sets it as the current editor. If text is passed in,
|
||||
it will replace the text present in the HTML template.
|
||||
|
||||
text: optional argument to override the text passed in via the HTML template
|
||||
###
|
||||
createXMLEditor: (text) ->
|
||||
@xml_editor = CodeMirror.fromTextArea($(".xml-box", @element)[0], {
|
||||
mode: "xml"
|
||||
lineNumbers: true
|
||||
lineWrapping: true
|
||||
})
|
||||
if text
|
||||
@xml_editor.setValue(text)
|
||||
@setCurrentEditor(@xml_editor)
|
||||
|
||||
###
|
||||
User has clicked to show the XML editor. Before XML editor is swapped in,
|
||||
the user will need to confirm the one-way conversion.
|
||||
###
|
||||
onShowXMLButton: (e) =>
|
||||
e.preventDefault();
|
||||
if @confirmConversionToXml()
|
||||
@createXMLEditor(MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue()))
|
||||
# Need to refresh to get line numbers to display properly (and put cursor position to 0)
|
||||
@xml_editor.setCursor(0)
|
||||
@xml_editor.refresh()
|
||||
# Hide markdown-specific toolbar buttons
|
||||
$(@element.find('.editor-bar')).hide()
|
||||
|
||||
###
|
||||
Have the user confirm the one-way conversion to XML.
|
||||
Returns true if the user clicked OK, else false.
|
||||
###
|
||||
confirmConversionToXml: ->
|
||||
# TODO: use something besides a JavaScript confirm dialog?
|
||||
return confirm("If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?")
|
||||
|
||||
###
|
||||
Event listener for toolbar buttons (only possible when markdown editor is visible).
|
||||
###
|
||||
onToolbarButton: (e) =>
|
||||
e.preventDefault();
|
||||
selection = @markdown_editor.getSelection()
|
||||
revisedSelection = null
|
||||
switch $(e.currentTarget).attr('class')
|
||||
when "multiple-choice-button" then revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection)
|
||||
when "string-button" then revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection)
|
||||
when "number-button" then revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection)
|
||||
when "checks-button" then revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection)
|
||||
when "dropdown-button" then revisedSelection = MarkdownEditingDescriptor.insertSelect(selection)
|
||||
when "header-button" then revisedSelection = MarkdownEditingDescriptor.insertHeader(selection)
|
||||
when "explanation-button" then revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection)
|
||||
else # ignore click
|
||||
|
||||
if revisedSelection != null
|
||||
@markdown_editor.replaceSelection(revisedSelection)
|
||||
@markdown_editor.focus()
|
||||
|
||||
###
|
||||
Event listener for toggling cheatsheet (only possible when markdown editor is visible).
|
||||
###
|
||||
toggleCheatsheet: (e) =>
|
||||
e.preventDefault();
|
||||
if !$(@markdown_editor.getWrapperElement()).find('.simple-editor-cheatsheet')[0]
|
||||
@cheatsheet = $($('#simple-editor-cheatsheet').html())
|
||||
$(@markdown_editor.getWrapperElement()).append(@cheatsheet)
|
||||
|
||||
setTimeout (=> @cheatsheet.toggleClass('shown')), 10
|
||||
|
||||
###
|
||||
Stores the current editor and hides the one that is not displayed.
|
||||
###
|
||||
setCurrentEditor: (editor) ->
|
||||
if @current_editor
|
||||
$(@current_editor.getWrapperElement()).hide()
|
||||
@current_editor = editor
|
||||
$(@current_editor.getWrapperElement()).show()
|
||||
$(@current_editor).focus();
|
||||
|
||||
###
|
||||
Called when save is called. Listeners are unregistered because editing the block again will
|
||||
result in a new instance of the descriptor. Note that this is NOT the case for cancel--
|
||||
when cancel is called the instance of the descriptor is reused if edit is selected again.
|
||||
###
|
||||
save: ->
|
||||
@element.off('click', '.xml-tab', @changeEditor)
|
||||
@element.off('click', '.format-buttons a', @onToolbarButton)
|
||||
@element.off('click', '.cheatsheet-toggle', @toggleCheatsheet)
|
||||
if @current_editor == @markdown_editor
|
||||
{
|
||||
data: MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue())
|
||||
metadata:
|
||||
markdown: @markdown_editor.getValue()
|
||||
}
|
||||
else
|
||||
{
|
||||
data: @xml_editor.getValue()
|
||||
metadata:
|
||||
markdown: null
|
||||
}
|
||||
|
||||
@insertMultipleChoice: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '(', ')', MarkdownEditingDescriptor.multipleChoiceTemplate)
|
||||
|
||||
@insertCheckboxChoice: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '[', ']', MarkdownEditingDescriptor.checkboxChoiceTemplate)
|
||||
|
||||
@insertGenericChoice: (selectedText, choiceStart, choiceEnd, template) ->
|
||||
if selectedText.length > 0
|
||||
# Replace adjacent newlines with a single newline, strip any trailing newline
|
||||
cleanSelectedText = selectedText.replace(/\n+/g, '\n').replace(/\n$/,'')
|
||||
lines = cleanSelectedText.split('\n')
|
||||
revisedLines = ''
|
||||
for line in lines
|
||||
revisedLines += choiceStart
|
||||
# a stand alone x before other text implies that this option is "correct"
|
||||
if /^\s*x\s+(\S)/i.test(line)
|
||||
# Remove the x and any initial whitespace as long as there's more text on the line
|
||||
line = line.replace(/^\s*x\s+(\S)/i, '$1')
|
||||
revisedLines += 'x'
|
||||
else
|
||||
revisedLines += ' '
|
||||
revisedLines += choiceEnd + ' ' + line + '\n'
|
||||
return revisedLines
|
||||
else
|
||||
return template
|
||||
|
||||
@insertStringInput: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.stringInputTemplate)
|
||||
|
||||
@insertNumberInput: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.numberInputTemplate)
|
||||
|
||||
@insertSelect: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[[', ']]', MarkdownEditingDescriptor.selectTemplate)
|
||||
|
||||
@insertHeader: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '', '\n====\n', MarkdownEditingDescriptor.headerTemplate)
|
||||
|
||||
@insertExplanation: (selectedText) ->
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[explanation]\n', '\n[explanation]', MarkdownEditingDescriptor.explanationTemplate)
|
||||
|
||||
@insertGenericInput: (selectedText, lineStart, lineEnd, template) ->
|
||||
if selectedText.length > 0
|
||||
# TODO: should this insert a newline afterwards?
|
||||
return lineStart + selectedText + lineEnd
|
||||
else
|
||||
return template
|
||||
|
||||
# We may wish to add insertHeader. Here is Tom's code.
|
||||
# function makeHeader() {
|
||||
# var selection = simpleEditor.getSelection();
|
||||
# var revisedSelection = selection + '\n';
|
||||
# for(var i = 0; i < selection.length; i++) {
|
||||
#revisedSelection += '=';
|
||||
# }
|
||||
# simpleEditor.replaceSelection(revisedSelection);
|
||||
#}
|
||||
#
|
||||
@markdownToXml: (markdown)->
|
||||
toXml = `function(markdown) {
|
||||
var xml = markdown;
|
||||
|
||||
// replace headers
|
||||
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h1>$1</h1>');
|
||||
xml = xml.replace(/\n^\=\=+$/gm, '');
|
||||
|
||||
// group multiple choice answers
|
||||
xml = xml.replace(/(^\s*\(.?\).*?$\n*)+/gm, function(match, p) {
|
||||
var groupString = '<multiplechoiceresponse>\n';
|
||||
groupString += ' <choicegroup type="MultipleChoice">\n';
|
||||
var options = match.split('\n');
|
||||
for(var i = 0; i < options.length; i++) {
|
||||
if(options[i].length > 0) {
|
||||
var value = options[i].split(/^\s*\(.?\)\s*/)[1];
|
||||
var correct = /^\s*\(x\)/i.test(options[i]);
|
||||
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
|
||||
}
|
||||
}
|
||||
groupString += ' </choicegroup>\n';
|
||||
groupString += '</multiplechoiceresponse>\n\n';
|
||||
return groupString;
|
||||
});
|
||||
|
||||
// group check answers
|
||||
xml = xml.replace(/(^\s*\[.?\].*?$\n*)+/gm, function(match, p) {
|
||||
var groupString = '<choiceresponse>\n';
|
||||
groupString += ' <checkboxgroup direction="vertical">\n';
|
||||
var options = match.split('\n');
|
||||
for(var i = 0; i < options.length; i++) {
|
||||
if(options[i].length > 0) {
|
||||
var value = options[i].split(/^\s*\[.?\]\s*/)[1];
|
||||
var correct = /^\s*\[x\]/i.test(options[i]);
|
||||
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
|
||||
}
|
||||
}
|
||||
groupString += ' </checkboxgroup>\n';
|
||||
groupString += '</choiceresponse>\n\n';
|
||||
return groupString;
|
||||
});
|
||||
|
||||
// replace string and numerical
|
||||
xml = xml.replace(/^\=\s*(.*?$)/gm, function(match, p) {
|
||||
var string;
|
||||
var floatValue = parseFloat(p);
|
||||
if(!isNaN(floatValue)) {
|
||||
var params = /(.*?)\+\-\s*(.*?$)/.exec(p);
|
||||
if(params) {
|
||||
string = '<numericalresponse answer="' + floatValue + '">\n';
|
||||
string += ' <responseparam type="tolerance" default="' + params[2] + '" />\n';
|
||||
} else {
|
||||
string = '<numericalresponse answer="' + floatValue + '">\n';
|
||||
}
|
||||
string += ' <textline />\n';
|
||||
string += '</numericalresponse>\n\n';
|
||||
} else {
|
||||
string = '<stringresponse answer="' + p + '" type="ci">\n <textline size="20"/>\n</stringresponse>\n\n';
|
||||
}
|
||||
return string;
|
||||
});
|
||||
|
||||
// replace selects
|
||||
xml = xml.replace(/\[\[(.+?)\]\]/g, function(match, p) {
|
||||
var selectString = '\n<optionresponse>\n';
|
||||
selectString += ' <optioninput options="(';
|
||||
var options = p.split(/\,\s*/g);
|
||||
for(var i = 0; i < options.length; i++) {
|
||||
selectString += "'" + options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, '$1') + "'" + (i < options.length -1 ? ',' : '');
|
||||
}
|
||||
selectString += ')" correct="';
|
||||
var correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(p);
|
||||
if (correct) selectString += correct[1];
|
||||
selectString += '"></optioninput>\n';
|
||||
selectString += '</optionresponse>\n\n';
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// replace explanations
|
||||
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
|
||||
var selectString = '<solution>\n<div class="detailed-solution">\nExplanation\n\n' + p1 + '\n</div>\n</solution>';
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// split scripts and wrap paragraphs
|
||||
var splits = xml.split(/(\<\/?script.*?\>)/g);
|
||||
var scriptFlag = false;
|
||||
for(var i = 0; i < splits.length; i++) {
|
||||
if(/\<script/.test(splits[i])) {
|
||||
scriptFlag = true;
|
||||
}
|
||||
if(!scriptFlag) {
|
||||
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, '<p>$1</p>');
|
||||
}
|
||||
if(/\<\/script/.test(splits[i])) {
|
||||
scriptFlag = false;
|
||||
}
|
||||
}
|
||||
xml = splits.join('');
|
||||
|
||||
// rid white space
|
||||
xml = xml.replace(/\n\n\n/g, '\n');
|
||||
|
||||
// surround w/ problem tag
|
||||
xml = '<problem>\n' + xml + '\n</problem>';
|
||||
|
||||
return xml;
|
||||
}
|
||||
`
|
||||
return toXml markdown
|
||||
|
||||
@@ -587,3 +587,6 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
|
||||
has_score = True
|
||||
always_recalculate_grades = True
|
||||
template_dir_name = "peer_grading"
|
||||
|
||||
#Specify whether or not to pass in open ended interface
|
||||
needs_open_ended_interface = True
|
||||
|
||||
@@ -216,20 +216,26 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours
|
||||
#It needs the open ended grading interface in order to get peer grading to be done
|
||||
#TODO: refactor these settings into module-specific settings when possible.
|
||||
#this first checks to see if the descriptor is the correct one, and only sends settings if it is
|
||||
is_descriptor_combined_open_ended = (descriptor.__class__.__name__ == 'CombinedOpenEndedDescriptor')
|
||||
is_descriptor_peer_grading = (descriptor.__class__.__name__ == 'PeerGradingDescriptor')
|
||||
|
||||
#Get descriptor metadata fields indicating needs for various settings
|
||||
needs_open_ended_interface = descriptor.needs_open_ended_interface
|
||||
needs_s3_interface = descriptor.needs_s3_interface
|
||||
|
||||
#Initialize interfaces to None
|
||||
open_ended_grading_interface = None
|
||||
s3_interface = None
|
||||
if is_descriptor_combined_open_ended or is_descriptor_peer_grading:
|
||||
|
||||
#Create interfaces if needed
|
||||
if needs_open_ended_interface:
|
||||
open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
|
||||
open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
|
||||
open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
|
||||
if is_descriptor_combined_open_ended:
|
||||
s3_interface = {
|
||||
'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''),
|
||||
'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''),
|
||||
'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','openended')
|
||||
}
|
||||
if needs_s3_interface:
|
||||
s3_interface = {
|
||||
'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''),
|
||||
'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''),
|
||||
'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','openended')
|
||||
}
|
||||
|
||||
def inner_get_module(descriptor):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user