Commit 4c80325d by Vik Paruchuri

Start working on COE editor, fix name checking in module_render

parent f8e9c4d4
......@@ -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
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):
"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment