diff --git a/askbot b/askbot
new file mode 160000
index 0000000000..e56ae38084
--- /dev/null
+++ b/askbot
@@ -0,0 +1 @@
+Subproject commit e56ae380846f7c6cdaeacfc58880fab103540491
diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee
index fe8f928746..dbeffa2738 100644
--- a/cms/static/coffee/src/views/unit.coffee
+++ b/cms/static/coffee/src/views/unit.coffee
@@ -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'
@@ -68,6 +69,52 @@ class CMS.Views.UnitEdit extends Backbone.View
@$newComponentItem.removeClass('adding')
@$newComponentItem.find('.rendered-component').remove()
+ addNewComponent: (event) =>
+ event.preventDefault()
+
+ @$componentItem = $('
').addClass('editing')
+ 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)
+ when 'html'
+ @$editor = $($('#html-editor').html())
+ $preview = $('')
+ initHTMLEditor(@$editor, $preview)
+ when 'discussion'
+ @$editor = $($('#discussion-editor').html())
+ $preview = $($('#discussion-preview').html())
+
+ @$editor.find('.save-button, .cancel-button').bind('click', =>
+ @$componentItem.removeClass('editing')
+ @closeEditor()
+ )
+
+ $componentActions = $($('#component-actions').html())
+
+ @$componentItem.append(@$editor)
+ @$componentItem.append($preview)
+
+ @$componentItem.append($componentActions)
+ @$componentItem.hide()
+ @$newComponentItem.before(@$componentItem)
+ @$componentItem.show()
+ $modalCover.fadeIn(200)
+ $modalCover.bind('click', @closeEditor)
+
+ closeEditor: (event) =>
+ @$editor.slideUp(150)
+ $modalCover.fadeOut(150)
+ $modalCover.unbind('click', @closeEditor)
+ @$editor.slideUp(150)
+ @$componentItem.removeClass('editing')
+
saveNewComponent: (event) =>
event.preventDefault()
diff --git a/cms/static/css/html-editor.css b/cms/static/css/html-editor.css
new file mode 100644
index 0000000000..e8ccbb11d0
--- /dev/null
+++ b/cms/static/css/html-editor.css
@@ -0,0 +1,45 @@
+body {
+ background-color: #fff;
+ font-family: 'Open Sans', Verdana, Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ line-height: 1.6;
+ color: #3c3c3c;
+ scrollbar-3dlight-color: #F0F0EE;
+ scrollbar-arrow-color: #676662;
+ scrollbar-base-color: #F0F0EE;
+ scrollbar-darkshadow-color: #DDDDDD;
+ scrollbar-face-color: #E0E0DD;
+ scrollbar-highlight-color: #F0F0EE;
+ scrollbar-shadow-color: #F0F0EE;
+ scrollbar-track-color: #F5F5F5;
+}
+
+a {
+ color: #1d9dd9;
+ text-decoration: none;
+}
+
+p {
+ font-size: 16px;
+ line-height: 1.6;
+}
+
+h2 {
+ color: #646464;
+ font-size: 19px;
+ font-weight: 300;
+ letter-spacing: 1px;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+}
+
+code {
+ margin: 0 2px;
+ padding: 0px 5px;
+ border-radius: 3px;
+ border: 1px solid #eaeaea;
+ white-space: nowrap;
+ font-family: Monaco, monospace;
+ font-size: 14px;
+ background-color: #f8f8f8;
+}
\ No newline at end of file
diff --git a/cms/static/img/choice-example.png b/cms/static/img/choice-example.png
new file mode 100644
index 0000000000..ee136577a9
Binary files /dev/null and b/cms/static/img/choice-example.png differ
diff --git a/cms/static/img/multi-example.png b/cms/static/img/multi-example.png
new file mode 100644
index 0000000000..abe729a94b
Binary files /dev/null and b/cms/static/img/multi-example.png differ
diff --git a/cms/static/img/new-post-icon.png b/cms/static/img/new-post-icon.png
new file mode 100644
index 0000000000..bf16b9da89
Binary files /dev/null and b/cms/static/img/new-post-icon.png differ
diff --git a/cms/static/img/number-example.png b/cms/static/img/number-example.png
new file mode 100644
index 0000000000..7cd050cb5e
Binary files /dev/null and b/cms/static/img/number-example.png differ
diff --git a/cms/static/img/problem-editor-icons.png b/cms/static/img/problem-editor-icons.png
new file mode 100644
index 0000000000..62f078560f
Binary files /dev/null and b/cms/static/img/problem-editor-icons.png differ
diff --git a/cms/static/img/problem-settings-icon.png b/cms/static/img/problem-settings-icon.png
new file mode 100644
index 0000000000..f99082a1e7
Binary files /dev/null and b/cms/static/img/problem-settings-icon.png differ
diff --git a/cms/static/img/select-example.png b/cms/static/img/select-example.png
new file mode 100644
index 0000000000..ef80e629de
Binary files /dev/null and b/cms/static/img/select-example.png differ
diff --git a/cms/static/img/show-hide-discussion-icon.png b/cms/static/img/show-hide-discussion-icon.png
new file mode 100644
index 0000000000..17d3193780
Binary files /dev/null and b/cms/static/img/show-hide-discussion-icon.png differ
diff --git a/cms/static/img/string-example.png b/cms/static/img/string-example.png
new file mode 100644
index 0000000000..6f628b20d4
Binary files /dev/null and b/cms/static/img/string-example.png differ
diff --git a/cms/static/img/visual-editor-image-icon.png b/cms/static/img/visual-editor-image-icon.png
new file mode 100644
index 0000000000..17f8a5bfb4
Binary files /dev/null and b/cms/static/img/visual-editor-image-icon.png differ
diff --git a/cms/static/js/html-editor.js b/cms/static/js/html-editor.js
new file mode 100644
index 0000000000..3a4ab47172
--- /dev/null
+++ b/cms/static/js/html-editor.js
@@ -0,0 +1,84 @@
+var $body;
+var $htmlPreview;
+var $htmlEditor;
+var $visualEditor;
+var $assetWidget;
+var visualEditor;
+var htmlEditor;
+
+function initHTMLEditor($editor, $prev) {
+ $htmlEditor = $editor;
+ $htmlPreview = $prev;
+
+ // there's a race condition here. wait a bit, then init tiny
+ setTimeout(function() {
+ $visualEditor = $editor.find('.edit-box.tinymce').tinymce({
+ script_url : '/static/js/tiny_mce/tiny_mce.js',
+ theme : "advanced",
+ skin: 'studio',
+
+ // we may want to add "styleselect" when we collect all styles used throught the lms
+ theme_advanced_buttons1 : "formatselect,bold,italic,underline,bullist,numlist,outdent,indent,blockquote,studio.asset,link,unlink",
+ theme_advanced_toolbar_location : "top",
+ theme_advanced_toolbar_align : "left",
+ theme_advanced_statusbar_location : "none",
+ theme_advanced_resizing : true,
+ theme_advanced_blockformats : "p,code,h2,h3,h4,h5,h6,blockquote",
+ content_css : "/static/css/html-editor.css",
+ width: '100%',
+ height: '400px',
+ setup : function(ed) {
+ ed.addButton('studio.asset', {
+ title : 'Add Asset',
+ image : '/static/img/visual-editor-image-icon.png',
+ onclick : function() {
+ $assetWidget = $($('#asset-library-widget').html());
+ $assetWidget.find('.close-button').bind('click', closeAssetWidget);
+ $modalCover.unbind('click');
+ $modalCover.bind('click', closeAssetWidget);
+ $modalCover.css('z-index', '99999');
+ $('.insert-asset-button', $assetWidget).bind('click', { editor: ed }, insertAsset);
+ $body.append($assetWidget);
+ }
+ });
+ }
+ });
+ }, 100);
+
+ htmlEditor = CodeMirror.fromTextArea($editor.find('.html-box')[0], {
+ lineWrapping: true,
+ mode: 'text/html',
+ lineNumbers: true
+ });
+
+ $editor.find('.save-button, .cancel-button').bind('click', updateHTMLPreview);
+}
+
+function insertAsset(e) {
+ closeAssetWidget();
+ var editor = e.data.editor;
+ editor.focus();
+ editor.selection.setContent($(this).attr('data-markup'));
+}
+
+function closeAssetWidget(e) {
+ $assetWidget.remove();
+ $modalCover.css('z-index', '1000');
+}
+
+function convertVisualToHTML() {
+ console.log('convert');
+ htmlEditor.setValue($visualEditor.html());
+}
+
+function convertHTMLToVisual() {
+ $visualEditor.html(htmlEditor.getValue());
+}
+
+function updateHTMLPreview() {
+ if(currentEditor == htmlEditor) {
+ $htmlPreview.html(htmlEditor.getValue());
+ } else {
+ $htmlPreview.html($visualEditor.html());
+ }
+}
\ No newline at end of file
diff --git a/cms/static/js/speed-editor.js b/cms/static/js/speed-editor.js
new file mode 100644
index 0000000000..f310e86cac
--- /dev/null
+++ b/cms/static/js/speed-editor.js
@@ -0,0 +1,431 @@
+var $body;
+var $preview;
+var $tooltip;
+var $cheatsheet;
+var $currentEditor;
+var simpleEditor;
+var xmlEditor;
+var currentEditor;
+var controlDown;
+var commandDown;
+
+
+(function() {
+ $body.on('click', '.editor-bar a', onEditorButton);
+ $body.on('click', '.cheatsheet-toggle', toggleCheatsheet);
+ $body.on('click', '.problem-settings-button', toggleProblemSettings);
+ $(document).bind('keyup', onKeyboard);
+})();
+
+function initProblemEditors($editor, $prev) {
+ $currentEditor = $editor;
+ 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');
+}
+
+function toggleProblemSettings(e) {
+ e.preventDefault();
+
+ $(this).toggleClass('is-open');
+
+ if($(this).hasClass('is-open')) {
+ $(this).find('.button-label').html('Hide Advanced Settings');
+ $('.problem-settings').slideDown(150);
+ } else {
+ $(this).find('.button-label').html('Show Advanced Settings');
+ $('.problem-settings').slideUp(150);
+ }
+}
+
+function toggleCheatsheet(e) {
+ e.preventDefault();
+
+ if(!$currentEditor.find('.simple-editor-cheatsheet')[0]) {
+ $cheatsheet = $($('#simple-editor-cheatsheet').html());
+ $currentEditor.append($cheatsheet);
+ }
+
+ setTimeout(function() {
+ $cheatsheet.toggleClass('shown');
+ }, 10);
+}
+
+function setFocus(e) {
+ $(simpleEditor).focus();
+}
+
+function onSimpleEditorUpdate() {
+ console.log('update');
+ updatePreview();
+ updateXML();
+}
+
+function updateXML() {
+ var val = simpleEditor.getValue();
+ var xml = val;
+
+ // replace headers
+ xml = xml.replace(/(^.*)\n(?=\=\=+)/g, '$1
');
+ xml = xml.replace(/\=\=+/g, '');
+
+ // group multiple choice answers
+ xml = xml.replace(/(\(.?\).*\n*)+/g, function(match, p) {
+ var groupString = '\n';
+ groupString += ' \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 += ' ' + value + '\n';
+ }
+ }
+ groupString += ' \n';
+ groupString += '\n\n';
+ return groupString;
+ });
+
+ // group check answers
+ xml = xml.replace(/(\[.?\].*\n*)+/g, function(match, p) {
+ var groupString = '\n';
+ groupString += ' \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 += ' ' + value + '\n';
+ }
+ }
+ groupString += ' \n';
+ groupString += '\n\n';
+ return groupString;
+ });
+
+ // replace videos
+ xml = xml.replace(/\{\{video\s(.*)\}\}/g, '\n\n');
+
+ // replace string and numerical
+ xml = xml.replace(/(?:\n|^)\=\s*(.*)/g, function(match, p) {
+ var string;
+ var params = /(.*)\+\-\s*(.*)/.exec(p);
+ if(parseFloat(p)) {
+ if(params) {
+ string = '\n';
+ string += ' \n';
+ } else {
+ string = '\n';
+ }
+ string += ' \n';
+ string += '\n\n';
+ } else {
+ string = '\n \n\n\n';
+ }
+ return string;
+ });
+
+ // replace selects
+ xml = xml.replace(/\[\[(.+)\]\]/g, function(match, p) {
+ var selectString = '\n\n';
+ selectString += ' \n';
+ selectString += '\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(/\
+
+
+
+
+
+
+
+
+