added a ton of speed editor goodness

This commit is contained in:
Tom Giannattasio
2012-11-07 17:21:20 -05:00
parent 4e876b056e
commit 44fb9561e8
7 changed files with 765 additions and 49 deletions

View File

@@ -1,6 +1,7 @@
class CMS.Views.UnitEdit extends Backbone.View
events:
'click .new-component .new-component-type a': 'showComponentTemplates'
# 'click .new-component .new-component-type a': 'showComponentTemplates'
'click .new-component .new-component-type a': 'addNewComponent'
'click .new-component .cancel-button': 'closeNewComponent'
'click .new-component-templates .new-component-template a': 'saveNewComponent'
'click .new-component-templates .cancel-button': 'closeNewComponent'
@@ -79,6 +80,42 @@ class CMS.Views.UnitEdit extends Backbone.View
@$newComponentItem.find('.rendered-component').remove()
@$newComponentItem.css('height', @$newComponentButton.outerHeight())
addNewComponent: (event) =>
event.preventDefault()
@$componentItem = $('<li>').addClass('editing')
$modalCover.show()
$modalCover.bind('click', @closeEditor)
type = $(event.currentTarget).data('type')
switch type
when 'video'
@$editor = $($('#video-editor').html())
$preview = $($('#video-preview').html())
when 'problem'
@$editor = $($('#problem-editor').html())
$preview = $($('#problem-preview').html())
initProblemEditors(@$editor, $preview)
@$editor.find('.save-button').bind('click', =>
@$componentItem.removeClass('editing')
@closeEditor()
)
$componentActions = $($('#component-actions').html());
@$componentItem.append(@$editor)
@$componentItem.append($preview)
@$componentItem.append($componentActions);
@$newComponentItem.before(@$componentItem)
closeEditor: (event) =>
console.log('close')
$modalCover.hide()
$modalCover.unbind('click', @closeEditor)
@$editor.remove()
@$componentItem.removeClass('editing')
saveNewComponent: (event) =>
event.preventDefault()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,421 @@
var $body;
var $preview;
var $tooltip;
var simpleEditor;
var xmlEditor;
var currentEditor;
var controlDown;
var commandDown;
function initProblemEditors($editor, $prev) {
simpleEditor = CodeMirror.fromTextArea($editor.find('.edit-box')[0], {
lineWrapping: true,
extraKeys: {
'Ctrl-N': newUnit,
'Ctrl-H': makeHeader,
'Ctrl-V': makeVideo,
'Ctrl-M': makeMultipleChoice,
'Ctrl-C': makeCheckboxes,
'Ctrl-S': makeStringInput,
'Shift-Ctrl-3': makeNumberInput,
'Shift-Ctrl-S': makeSelect
},
mode: null,
onChange: onSimpleEditorUpdate
});
xmlEditor = CodeMirror.fromTextArea($editor.find('.xml-box')[0], {
lineWrapping: true,
mode: 'xml',
lineNumbers: true
});
currentEditor = simpleEditor;
$(simpleEditor.getWrapperElement()).css('background', '#fff');
$(xmlEditor.getWrapperElement()).css({
'background': '#fff'
}).hide();
$(simpleEditor.getWrapperElement()).bind('click', setFocus);
$preview = $prev.find('.problem');
$body.on('click', '.editor-bar a', onEditorButton);
$body.on('click', '.editor-tabs a', setEditorTab);
$(document).bind('keyup', onKeyboard);
}
function setEditorTab(e) {
e.preventDefault();
$('.editor-tabs .current').removeClass('current');
$(this).addClass('current');
if($(this).hasClass('simple-tab')) {
currentEditor = simpleEditor;
$(simpleEditor.getWrapperElement()).show();
$(xmlEditor.getWrapperElement()).hide();
$(simpleEditor).focus();
} else {
currentEditor = xmlEditor;
$(simpleEditor.getWrapperElement()).hide();
$(xmlEditor.getWrapperElement()).show();
$(xmlEditor).focus();
}
onSimpleEditorUpdate();
}
function setFocus(e) {
$(simpleEditor).focus();
}
function onSimpleEditorUpdate() {
updatePreview();
updateXML();
}
function updateXML() {
var val = simpleEditor.getValue();
var xml = val;
// replace headers
xml = xml.replace(/(^.*)\n(?=\=\=+)/g, '<h1>$1</h1>');
xml = xml.replace(/\=\=+/g, '');
// group multiple choice answers
xml = xml.replace(/(\(.?\).*\n*)+/g, 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*/)[1];
var correct = /\(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(/(\[.?\].*\n*)+/g, function(match, p) {
var groupString = '<multiplechoiceresponse>\n';
groupString += ' <choicegroup type="MultipleChoiceChecks">\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*/)[1];
var correct = /\[x\]/i.test(options[i]);
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
}
}
groupString += ' </choicegroup>\n';
groupString += '</multiplechoiceresponse>\n\n';
return groupString;
});
// replace videos
xml = xml.replace(/\{\{video\s(.*)\}\}/g, '<video youtube="1.0:$1" />\n\n');
// replace string input
xml = xml.replace(/\_\_\_\[(.+)\]/g, '<stringresponse answer="$1" type="ci">\n <textline size="20"/>\n</stringresponse>\n\n');
// replace numerical input
xml = xml.replace(/\#\#\#\[(.+)\]/g, function(match, p) {
var solution = extractNumericalSolution(p);
var string = '<numericalresponse answer="' + solution + '">\n';
var params = extractNumericalSettings(p);
if(params) {
for(var i = 0; i < params.length; i++) {
var splitProp = params[i].split(/:\s*/);
string += ' <responseparam type="' + splitProp[0] + '" default="' + splitProp[1] + '" />\n';
}
}
string += ' <textline />\n';
string += '</numericalresponse>\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(/\((.*)\)/g, '$1') + "'" + (i < options.length -1 ? ',' : '');
}
selectString += ')" correct="';
selectString += p.split(/\(/)[1].split(/\)/)[0];
selectString += '"></optioninput>\n';
selectString += '</optionresponse>\n\n';
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');
// console.log(xml);
xmlEditor.setValue(xml);
}
function updatePreview() {
var val = simpleEditor.getValue();
var html = val;
// replace headers
html = html.replace(/(^.*)\n(?=\=\=+)/g, '<h1>$1</h1>');
html = html.replace(/\=\=+/g, '');
// group multiple choice answers
html = html.replace(/(\(.?\).*\n*)+/g, function(match, p) {
var groupString = '<form class="choicegroup">\n';
groupString += ' <div class="indicator_container"><span class="unanswered" id=""></span></div>';
groupString += ' <fieldset>\n';
var options = match.split('\n');
for(var i = 0; i < options.length; i++) {
if(options[i].length > 0) {
// groupString += ' <li>' + options[i] + '</li>\n';
groupString += ' <label>' + options[i] + '</label>\n';
}
}
groupString += ' </fieldset>\n';
groupString += '</form>\n';
return groupString;
});
// group check answers
html = html.replace(/(\[.?\].*\n*)+/g, function(match, p) {
var groupString = '<ul class="check-choice-group">\n';
var options = match.split('\n');
for(var i = 0; i < options.length; i++) {
if(options[i].length > 0) {
groupString += ' <li>' + options[i] + '</li>\n';
}
}
groupString += '</ul>\n';
return groupString;
});
// wrap the paragraphs
html = html.replace(/(^(?!\<\/*ul|\s*\<li|\<h1|\s*\<label|\s*$).*$)/gm, '<p>$1</p>\n');
// replace videos
html = html.replace(/\{\{video\s(.*)\}\}/g, function(match, p) {
var id = p.replace(/.*1\.0\:/g, '').replace(/\,.*/g, '');
var string = '<iframe width="420" height="315" src="http://www.youtube.com/embed/' + id + '" frameborder="0" allowfullscreen></iframe>';
return string;
});
// replace checkboxes
html = html.replace(/\[\s*\]/g, '<input type="checkbox">');
html = html.replace(/\[x\]/gi, '<input type="checkbox" checked>');
// replace radios
html = html.replace(/\(\s*\)/g, '<input type="radio" name="multiple-choice-id">');
html = html.replace(/\(x\)/gi, '<input type="radio" checked name="multiple-choice-id">');
// replace string input
html = html.replace(/\_\_\_\[(.+)\]/g, function(match, p) {
var string = '<input type="text" name="" id="" value="' + p + '" size="20">\n';
return string;
});
// replace numerical input
html = html.replace(/\#\#\#\[(.+)\]/g, '<input type="text"><small>$1</small>');
// replace selects
html = html.replace(/\[\[(.+)\]\]/g, function(match, p) {
var selectString = '<select>\n';
selectString += ' <option disabled selected></option>\n';
var options = p.split(/\,\s*/g);
for(var i = 0; i < options.length; i++) {
var isAnswer = /\(.*\)/.test(options[i]);
selectString += ' <option data-answer="' + (isAnswer ? 'true' : 'false') + '"">' + options[i].replace(/\((.*)\)/g, '$1') + '</option>\n';
}
selectString += '</select>\n';
return selectString;
});
html = html.replace(/\n\n/g, '');
// console.log(html);
$preview.html(html);
}
function extractNumericalSolution(string) {
var solution = string.replace(/\s*\{.*/g, '');
return solution;
}
function extractNumericalSettings(string) {
var settings = string.match(/\{.*\}/g);
if(settings) {
settings = settings[0].replace(/\{|\}/g, '');
settings = settings.split(/\,\s*/g);
}
return settings;
}
function onEditorButton(e) {
e.preventDefault();
switch($(this).attr('class')) {
case 'multiple-choice-button':
makeMultipleChoice();
break;
case 'string-button':
makeStringInput();
break;
case 'number-button':
makeNumberInput();
break;
case 'checks-button':
makeCheckboxes();
break;
case 'dropdown-button':
makeSelect();
break;
}
}
function newUnit() {
window.location = 'index.html';
}
function makeHeader() {
var selection = simpleEditor.getSelection();
var revisedSelection = selection + '\n';
for(var i = 0; i < selection.length; i++) {
revisedSelection += '=';
}
simpleEditor.replaceSelection(revisedSelection);
}
function makeVideo() {
var selection = simpleEditor.getSelection();
simpleEditor.replaceSelection('{{video ' + selection + '}}');
}
function makeMultipleChoice() {
console.log(currentEditor);
var selection = simpleEditor.getSelection();
if(selection.length > 0) {
var cleanSelection = selection.replace(/\n\n/g, '\n');
var lines = cleanSelection.split('\n');
var revisedLines = '';
for(var i = 0; i < lines.length; i++) {
revisedLines += '(';
if(/x\s/i.test(lines[i])) {
revisedLines += 'x';
lines[i] = lines[i].replace(/x\s/i, '');
} else {
revisedLines += ' ';
}
revisedLines += ') ' + lines[i] + '\n';
}
simpleEditor.replaceSelection(revisedLines);
} else {
var template = '( ) incorrect\n';
template += '( ) incorrect\n';
template += '(x) correct\n';
simpleEditor.replaceSelection(template);
setFocus();
}
}
function makeStringInput() {
var selection = simpleEditor.getSelection();
if(selection.length > 0) {
var revisedSelection = '___[' + selection + ']';
simpleEditor.replaceSelection(revisedSelection);
} else {
var template = '___[answer]\n';
simpleEditor.replaceSelection(template);
setFocus();
}
}
function makeNumberInput() {
var selection = simpleEditor.getSelection();
if(selection.length > 0) {
var revisedSelection = '###[' + selection + ']';
simpleEditor.replaceSelection(revisedSelection);
} else {
var template = '###[answer +-tolerance]\n';
simpleEditor.replaceSelection(template);
setFocus();
}
}
function makeCheckboxes() {
var selection = simpleEditor.getSelection();
if(selection.length > 0) {
var cleanSelection = selection.replace(/\n\n/g, '\n');
var lines = cleanSelection.split('\n');
var revisedLines = '';
for(var i = 0; i < lines.length; i++) {
revisedLines += '[';
if(/x\s/i.test(lines[i])) {
revisedLines += 'x';
lines[i] = lines[i].replace(/x\s/i, '');
} else {
revisedLines += ' ';
}
revisedLines += '] ' + lines[i] + '\n';
}
simpleEditor.replaceSelection(revisedLines);
} else {
var template = '[x] correct\n';
template += '[ ] incorrect\n';
template += '[x] correct\n';
simpleEditor.replaceSelection(template);
setFocus();
}
}
function makeSelect() {
var selection = simpleEditor.getSelection();
if(selection.length > 0) {
var revisedSelection = '[[' + selection + ']]';
simpleEditor.replaceSelection(revisedSelection);
} else {
var template = '[[incorrect, (correct), incorrect]]\n';
simpleEditor.replaceSelection(template);
setFocus();
}
}
function onKeyboard(e) {
switch(e.keyCode) {
// n
case 78:
if(e.ctrlKey) {
e.preventDefault();
newUnit();
}
break;
}
}

View File

@@ -337,4 +337,30 @@ body.show-wip {
content: '';
@extend .spinner-icon;
}
}
.tooltip {
display: block;
position: absolute;
z-index: 9999999;
padding: 4px 8px;
background: rgba(0, 0, 0, .9);
border-radius: 3px;
pointer-events: none;
font-size: 11px;
color: #fff;
text-align: center;
&:before {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
width: 20px;
height: 10px;
margin-left: -10px;
font-size: 16px;
line-height: 0;
color: rgba(0, 0, 0, .9);
}
}

View File

@@ -277,3 +277,41 @@
vertical-align: middle;
background: url(../img/spinner-in-field.gif) no-repeat;
}
.problem-editor-icon {
display: inline-block;
width: 26px;
height: 21px;
vertical-align: middle;
background: url(../img/problem-editor-icons.png) no-repeat;
}
.problem-editor-icon.multiple-choice {
background-position: 0 0;
}
.problem-editor-icon.checks {
background-position: -56px 0;
}
.problem-editor-icon.string {
width: 28px;
background-position: -111px 0;
}
.problem-editor-icon.number {
width: 24px;
background-position: -168px 0;
}
.problem-editor-icon.dropdown {
width: 17px;
background-position: -220px 0;
}

View File

@@ -73,7 +73,11 @@
}
&.editing {
border-color: #6696d7;
border-color: #3c3c3c;
box-shadow: 0 0 30px rgba(0, 0, 0, .5);
position: relative;
z-index: 99999;
@include transition(all);
.drag-handle,
.component-actions {
@@ -135,30 +139,16 @@
}
&.new-component-item {
padding: 0;
border: 1px solid #8891a1;
border: none;
border-radius: 3px;
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
background-color: #d1dae3;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .2) inset);
@include transition(background-color .15s, border-color .15s);
&.adding {
background-color: $blue;
border-color: #437fbf;
}
.new-component-button {
display: block;
padding: 20px;
text-align: center;
color: #6d788b;
}
background: $lightGrey;
@include clearfix;
h5 {
margin-bottom: 8px;
color: #fff;
margin-bottom: 22px;
color: $blue;
font-weight: 700;
text-align: center;
}
.rendered-component {
@@ -168,25 +158,32 @@
}
.new-component-type, .new-component-template {
text-align: center;
@include clearfix;
li {
display: inline;
}
a {
position: relative;
float: left;
display: inline-block;
width: 100px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 8px;
border: 1px solid #437fbf;
background-color: $blue;
font-size: 13px;
line-height: 14px;
color: #fff;
text-align: center;
@include box-shadow(0 1px 1px rgba(0, 0, 0, .4), 0 1px 0 rgba(255, 255, 255, .4) inset);
@include box-shadow(0 1px 1px rgba(0, 0, 0, .25), 0 1px 0 rgba(255, 255, 255, .4) inset);
@include transition(background-color .15s);
&:hover {
background-color: rgba(255, 255, 255, .2);
@include box-shadow(0 1px 1px rgba(0, 0, 0, .4), 0 1px 0 rgba(255, 255, 255, .4) inset, 0 0 20px rgba(255, 255, 255, .3) inset);
}
.name {
@@ -208,9 +205,6 @@
.new-component,
.new-component-templates {
display: none;
position: absolute;
width: 100%;
padding: 20px;
@include clearfix;
@@ -219,6 +213,10 @@
border-color: #30649c;
}
}
.new-component-templates {
display: none;
}
}
}
}
@@ -227,6 +225,111 @@
padding: 10px 20px;
}
.editor {
background: $blue;
padding: 20px;
border-radius: 3px 3px 0 0;
label {
display: inline-block;
width: 130px;
color: #fff;
}
input {
border-color: #3c3c3c;
}
.row {
border-bottom: none;
padding-bottom: 0;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
.video-id-field,
.transcript-field {
width: 300px;
}
.browse-button {
@include blue-button;
border-color: #3c3c3c;
font-size: 12px;
}
.save-button {
@include orange-button;
margin: 10px 8px 0 0;
border-color: #3c3c3c;
}
.cancel-button {
@include white-button;
border-color: #3c3c3c;
}
.editor-bar {
position: relative;
@include linear-gradient(top, #d4dee8, #c9d5e2);
padding: 5px;
border: 1px solid #3c3c3c;
border-radius: 3px 3px 0 0;
border-bottom-color: #a5aaaf;
@include clearfix;
a {
display: block;
float: left;
padding: 3px 10px 7px;
margin-left: 7px;
border-radius: 2px;
&:hover {
background: rgba(255, 255, 255, .5);
}
}
.editor-tabs {
position: absolute;
top: 10px;
right: 10px;
li {
float: left;
}
a {
height: 24px;
padding: 7px 20px 3px;
border: 1px solid #a5aaaf;
border-radius: 3px 3px 0 0;
@include linear-gradient(top, rgba(0, 0, 0, 0) 87%, rgba(0, 0, 0, .06));
background-color: #e5ecf3;
font-size: 13px;
color: #3c3c3c;
box-shadow: 1px -1px 1px rgba(0, 0, 0, .05);
&.current {
background: #fff;
border-bottom-color: #fff;
}
}
}
}
.CodeMirror {
font-size: 13px;
border: 1px solid #3c3c3c;
border-top: none;
background: #fff;
color: #3c3c3c;
}
}
.component-editor {
@include edit-box;
display: none;