diff --git a/.gitignore b/.gitignore index 3b7223108b..2fd1ca0181 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ lms/lib/comment_client/python nosetests.xml cover_html/ .idea/ +chromedriver.log \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..253bae3686 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "common/test/phantom-jasmine"] + path = common/test/phantom-jasmine + url = https://github.com/jcarver989/phantom-jasmine.git \ No newline at end of file diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py index c2e8348a66..6995df06a8 100644 --- a/cms/djangoapps/contentstore/course_info_model.py +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -1,7 +1,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore -from lxml import etree +from lxml import html import re from django.http import HttpResponseBadRequest import logging @@ -24,9 +24,9 @@ def get_course_updates(location): # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. try: - course_html_parsed = etree.fromstring(course_updates.definition['data']) - except etree.XMLSyntaxError: - course_html_parsed = etree.fromstring("
    ") + course_html_parsed = html.fromstring(course_updates.definition['data']) + except: + course_html_parsed = html.fromstring("
      ") # Confirm that root is
        , iterate over
      1. , pull out

        subs and then rest of val course_upd_collection = [] @@ -39,7 +39,7 @@ def get_course_updates(location): # could enforce that update[0].tag == 'h2' content = update[0].tail else: - content = "\n".join([etree.tostring(ele) for ele in update[1:]]) + content = "\n".join([html.tostring(ele) for ele in update[1:]]) # make the id on the client be 1..len w/ 1 being the oldest and len being the newest course_upd_collection.append({"id" : location_base + "/" + str(len(course_html_parsed) - idx), @@ -61,17 +61,17 @@ def update_course_updates(location, update, passed_id=None): # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. try: - course_html_parsed = etree.fromstring(course_updates.definition['data']) - except etree.XMLSyntaxError: - course_html_parsed = etree.fromstring("
          ") + course_html_parsed = html.fromstring(course_updates.definition['data']) + except: + course_html_parsed = html.fromstring("
            ") # No try/catch b/c failure generates an error back to client - new_html_parsed = etree.fromstring('
          1. ' + update['date'] + '

            ' + update['content'] + '
          2. ') + new_html_parsed = html.fromstring('
          3. ' + update['date'] + '

            ' + update['content'] + '
          4. ') # Confirm that root is
              , iterate over
            1. , pull out

              subs and then rest of val if course_html_parsed.tag == 'ol': # ??? Should this use the id in the json or in the url or does it matter? - if passed_id: + if passed_id is not None: idx = get_idx(passed_id) # idx is count from end of list course_html_parsed[-idx] = new_html_parsed @@ -82,7 +82,7 @@ def update_course_updates(location, update, passed_id=None): passed_id = course_updates.location.url() + "/" + str(idx) # update db record - course_updates.definition['data'] = etree.tostring(course_html_parsed) + course_updates.definition['data'] = html.tostring(course_html_parsed) modulestore('direct').update_item(location, course_updates.definition['data']) return {"id" : passed_id, @@ -105,9 +105,9 @@ def delete_course_update(location, update, passed_id): # TODO use delete_blank_text parser throughout and cache as a static var in a class # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. try: - course_html_parsed = etree.fromstring(course_updates.definition['data']) - except etree.XMLSyntaxError: - course_html_parsed = etree.fromstring("
                ") + course_html_parsed = html.fromstring(course_updates.definition['data']) + except: + course_html_parsed = html.fromstring("
                  ") if course_html_parsed.tag == 'ol': # ??? Should this use the id in the json or in the url or does it matter? @@ -118,7 +118,7 @@ def delete_course_update(location, update, passed_id): course_html_parsed.remove(element_to_delete) # update db record - course_updates.definition['data'] = etree.tostring(course_html_parsed) + course_updates.definition['data'] = html.tostring(course_html_parsed) store = modulestore('direct') store.update_item(location, course_updates.definition['data']) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py new file mode 100644 index 0000000000..1f14e29083 --- /dev/null +++ b/cms/djangoapps/contentstore/features/common.py @@ -0,0 +1,127 @@ +from lettuce import world, step +from factories import * +from django.core.management import call_command +from lettuce.django import django_url +from django.conf import settings +from django.core.management import call_command +from nose.tools import assert_true +from nose.tools import assert_equal +import xmodule.modulestore.django + +from logging import getLogger +logger = getLogger(__name__) + +########### STEP HELPERS ############## +@step('I (?:visit|access|open) the Studio homepage$') +def i_visit_the_studio_homepage(step): + # To make this go to port 8001, put + # LETTUCE_SERVER_PORT = 8001 + # in your settings.py file. + world.browser.visit(django_url('/')) + assert world.browser.is_element_present_by_css('body.no-header', 10) + +@step('I am logged into Studio$') +def i_am_logged_into_studio(step): + log_into_studio() + +@step('I confirm the alert$') +def i_confirm_with_ok(step): + world.browser.get_alert().accept() + +@step(u'I press the "([^"]*)" delete icon$') +def i_press_the_category_delete_icon(step, category): + if category == 'section': + css = 'a.delete-button.delete-section-button span.delete-icon' + elif category == 'subsection': + css='a.delete-button.delete-subsection-button span.delete-icon' + else: + assert False, 'Invalid category: %s' % category + css_click(css) + +####### HELPER FUNCTIONS ############## +def create_studio_user( + uname='robot', + em='robot+studio@edx.org', + password='test'): + studio_user = UserFactory.build( + username=uname, + email=em) + studio_user.set_password(password) + studio_user.save() + + registration = RegistrationFactory(user=studio_user) + registration.register(studio_user) + registration.activate() + + user_profile = UserProfileFactory(user=studio_user) + +def flush_xmodule_store(): + # Flush and initialize the module store + # It needs the templates because it creates new records + # by cloning from the template. + # Note that if your test module gets in some weird state + # (though it shouldn't), do this manually + # from the bash shell to drop it: + # $ mongo test_xmodule --eval "db.dropDatabase()" + xmodule.modulestore.django._MODULESTORES = {} + xmodule.modulestore.django.modulestore().collection.drop() + xmodule.templates.update_templates() + +def assert_css_with_text(css,text): + assert_true(world.browser.is_element_present_by_css(css, 5)) + assert_equal(world.browser.find_by_css(css).text, text) + +def css_click(css): + world.browser.find_by_css(css).first.click() + +def css_fill(css, value): + world.browser.find_by_css(css).first.fill(value) + +def clear_courses(): + flush_xmodule_store() + +def fill_in_course_info( + name='Robot Super Course', + org='MITx', + num='101'): + css_fill('.new-course-name',name) + css_fill('.new-course-org',org) + css_fill('.new-course-number',num) + +def log_into_studio( + uname='robot', + email='robot+studio@edx.org', + password='test'): + create_studio_user(uname, email) + world.browser.cookies.delete() + world.browser.visit(django_url('/')) + world.browser.is_element_present_by_css('body.no-header', 10) + + login_form = world.browser.find_by_css('form#login_form') + login_form.find_by_name('email').fill(email) + login_form.find_by_name('password').fill(password) + login_form.find_by_name('submit').click() + + assert_true(world.browser.is_element_present_by_css('.new-course-button', 5)) + +def create_a_course(): + css_click('a.new-course-button') + fill_in_course_info() + css_click('input.new-course-save') + assert_true(world.browser.is_element_present_by_css('a#courseware-tab', 5)) + +def add_section(name='My Section'): + link_css = 'a.new-courseware-section-button' + css_click(link_css) + name_css = '.new-section-name' + save_css = '.new-section-name-save' + css_fill(name_css,name) + css_click(save_css) + +def add_subsection(name='Subsection One'): + css = 'a.new-subsection-item' + css_click(css) + name_css = 'input.new-subsection-name-input' + save_css = 'input.new-subsection-name-save' + css_fill(name_css, name) + css_click(save_css) \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/courses.feature b/cms/djangoapps/contentstore/features/courses.feature new file mode 100644 index 0000000000..39d39b50aa --- /dev/null +++ b/cms/djangoapps/contentstore/features/courses.feature @@ -0,0 +1,13 @@ +Feature: Create Course + In order offer a course on the edX platform + As a course author + I want to create courses + + Scenario: Create a course + Given There are no courses + And I am logged into Studio + When I click the New Course button + And I fill in the new course information + And I press the "Save" button + Then the Courseware page has loaded in Studio + And I see a link for adding a new section \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py new file mode 100644 index 0000000000..2c1cf6281a --- /dev/null +++ b/cms/djangoapps/contentstore/features/courses.py @@ -0,0 +1,50 @@ +from lettuce import world, step +from common import * + +############### ACTIONS #################### +@step('There are no courses$') +def no_courses(step): + clear_courses() + +@step('I click the New Course button$') +def i_click_new_course(step): + css_click('.new-course-button') + +@step('I fill in the new course information$') +def i_fill_in_a_new_course_information(step): + fill_in_course_info() + +@step('I create a new course$') +def i_create_a_course(step): + create_a_course() + +@step('I click the course link in My Courses$') +def i_click_the_course_link_in_my_courses(step): + course_css = 'span.class-name' + css_click(course_css) + +############ ASSERTIONS ################### +@step('the Courseware page has loaded in Studio$') +def courseware_page_has_loaded_in_studio(step): + courseware_css = 'a#courseware-tab' + assert world.browser.is_element_present_by_css(courseware_css) + +@step('I see the course listed in My Courses$') +def i_see_the_course_in_my_courses(step): + course_css = 'span.class-name' + assert_css_with_text(course_css,'Robot Super Course') + +@step('the course is loaded$') +def course_is_loaded(step): + class_css = 'a.class-name' + assert_css_with_text(class_css,'Robot Super Course') + +@step('I am on the "([^"]*)" tab$') +def i_am_on_tab(step, tab_name): + header_css = 'div.inner-wrapper h1' + assert_css_with_text(header_css,tab_name) + +@step('I see a link for adding a new section$') +def i_see_new_section_link(step): + link_css = 'a.new-courseware-section-button' + assert_css_with_text(link_css,'New Section') diff --git a/cms/djangoapps/contentstore/features/factories.py b/cms/djangoapps/contentstore/features/factories.py new file mode 100644 index 0000000000..389f2bac49 --- /dev/null +++ b/cms/djangoapps/contentstore/features/factories.py @@ -0,0 +1,31 @@ +import factory +from student.models import User, UserProfile, Registration +from datetime import datetime +import uuid + +class UserProfileFactory(factory.Factory): + FACTORY_FOR = UserProfile + + user = None + name = 'Robot Studio' + courseware = 'course.xml' + +class RegistrationFactory(factory.Factory): + FACTORY_FOR = Registration + + user = None + activation_key = uuid.uuid4().hex + +class UserFactory(factory.Factory): + FACTORY_FOR = User + + username = 'robot-studio' + email = 'robot+studio@edx.org' + password = 'test' + first_name = 'Robot' + last_name = 'Studio' + is_staff = False + is_active = True + is_superuser = False + last_login = datetime.now() + date_joined = datetime.now() \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature new file mode 100644 index 0000000000..ad00ba2911 --- /dev/null +++ b/cms/djangoapps/contentstore/features/section.feature @@ -0,0 +1,26 @@ +Feature: Create Section + In order offer a course on the edX platform + As a course author + I want to create and edit sections + + Scenario: Add a new section to a course + Given I have opened a new course in Studio + When I click the New Section link + And I enter the section name and click save + Then I see my section on the Courseware page + And I see a release date for my section + And I see a link to create a new subsection + + Scenario: Edit section release date + Given I have opened a new course in Studio + And I have added a new section + When I click the Edit link for the release date + And I save a new section release date + Then the section release date is updated + + Scenario: Delete section + Given I have opened a new course in Studio + And I have added a new section + When I press the "section" delete icon + And I confirm the alert + Then the section does not exist \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py new file mode 100644 index 0000000000..8ac30e2170 --- /dev/null +++ b/cms/djangoapps/contentstore/features/section.py @@ -0,0 +1,82 @@ +from lettuce import world, step +from common import * + +############### ACTIONS #################### +@step('I have opened a new course in Studio$') +def i_have_opened_a_new_course(step): + clear_courses() + log_into_studio() + create_a_course() + +@step('I click the new section link$') +def i_click_new_section_link(step): + link_css = 'a.new-courseware-section-button' + css_click(link_css) + +@step('I enter the section name and click save$') +def i_save_section_name(step): + name_css = '.new-section-name' + save_css = '.new-section-name-save' + css_fill(name_css,'My Section') + css_click(save_css) + +@step('I have added a new section$') +def i_have_added_new_section(step): + add_section() + +@step('I click the Edit link for the release date$') +def i_click_the_edit_link_for_the_release_date(step): + button_css = 'div.section-published-date a.edit-button' + css_click(button_css) + +@step('I save a new section release date$') +def i_save_a_new_section_release_date(step): + date_css = 'input.start-date.date.hasDatepicker' + time_css = 'input.start-time.time.ui-timepicker-input' + css_fill(date_css,'12/25/2013') + # click here to make the calendar go away + css_click(time_css) + css_fill(time_css,'12:00am') + css_click('a.save-button') + +############ ASSERTIONS ################### +@step('I see my section on the Courseware page$') +def i_see_my_section_on_the_courseware_page(step): + section_css = 'span.section-name-span' + assert_css_with_text(section_css,'My Section') + +@step('the section does not exist$') +def section_does_not_exist(step): + css = 'span.section-name-span' + assert world.browser.is_element_not_present_by_css(css) + +@step('I see a release date for my section$') +def i_see_a_release_date_for_my_section(step): + import re + + css = 'span.published-status' + assert world.browser.is_element_present_by_css(css) + status_text = world.browser.find_by_css(css).text + + # e.g. 11/06/2012 at 16:25 + msg = 'Will Release:' + date_regex = '[01][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]' + time_regex = '[0-2][0-9]:[0-5][0-9]' + match_string = '%s %s at %s' % (msg, date_regex, time_regex) + assert re.match(match_string,status_text) + +@step('I see a link to create a new subsection$') +def i_see_a_link_to_create_a_new_subsection(step): + css = 'a.new-subsection-item' + assert world.browser.is_element_present_by_css(css) + +@step('the section release date picker is not visible$') +def the_section_release_date_picker_not_visible(step): + css = 'div.edit-subsection-publish-settings' + assert False, world.browser.find_by_css(css).visible + +@step('the section release date is updated$') +def the_section_release_date_is_updated(step): + css = 'span.published-status' + status_text = world.browser.find_by_css(css).text + assert status_text == 'Will Release: 12/25/2013 at 12:00am' diff --git a/cms/djangoapps/contentstore/features/signup.feature b/cms/djangoapps/contentstore/features/signup.feature new file mode 100644 index 0000000000..8a6f93d33b --- /dev/null +++ b/cms/djangoapps/contentstore/features/signup.feature @@ -0,0 +1,12 @@ +Feature: Sign in + In order to use the edX content + As a new user + I want to signup for a student account + + Scenario: Sign up from the homepage + Given I visit the Studio homepage + When I click the link with the text "Sign up" + And I fill in the registration form + And I press the "Create My Account" button on the registration form + Then I should see be on the studio home page + And I should see the message "please click on the activation link in your email." \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py new file mode 100644 index 0000000000..7794511f94 --- /dev/null +++ b/cms/djangoapps/contentstore/features/signup.py @@ -0,0 +1,23 @@ +from lettuce import world, step + +@step('I fill in the registration form$') +def i_fill_in_the_registration_form(step): + register_form = world.browser.find_by_css('form#register_form') + register_form.find_by_name('email').fill('robot+studio@edx.org') + register_form.find_by_name('password').fill('test') + register_form.find_by_name('username').fill('robot-studio') + register_form.find_by_name('name').fill('Robot Studio') + register_form.find_by_name('terms_of_service').check() + +@step('I press the "([^"]*)" button on the registration form$') +def i_press_the_button_on_the_registration_form(step, button): + register_form = world.browser.find_by_css('form#register_form') + register_form.find_by_value(button).click() + +@step('I should see be on the studio home page$') +def i_should_see_be_on_the_studio_home_page(step): + assert world.browser.find_by_css('div.inner-wrapper') + +@step(u'I should see the message "([^"]*)"$') +def i_should_see_the_message(step, msg): + assert world.browser.is_text_present(msg, 5) \ No newline at end of file diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature new file mode 100644 index 0000000000..5acb5bfe44 --- /dev/null +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -0,0 +1,18 @@ +Feature: Create Subsection + In order offer a course on the edX platform + As a course author + I want to create and edit subsections + + Scenario: Add a new subsection to a section + Given I have opened a new course section in Studio + When I click the New Subsection link + And I enter the subsection name and click save + Then I see my subsection on the Courseware page + + Scenario: Delete a subsection + Given I have opened a new course section in Studio + And I have added a new subsection + And I see my subsection on the Courseware page + When I press the "subsection" delete icon + And I confirm the alert + Then the subsection does not exist diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py new file mode 100644 index 0000000000..ea614d3feb --- /dev/null +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -0,0 +1,39 @@ +from lettuce import world, step +from common import * + +############### ACTIONS #################### +@step('I have opened a new course section in Studio$') +def i_have_opened_a_new_course_section(step): + clear_courses() + log_into_studio() + create_a_course() + add_section() + +@step('I click the New Subsection link') +def i_click_the_new_subsection_link(step): + css = 'a.new-subsection-item' + css_click(css) + +@step('I enter the subsection name and click save$') +def i_save_subsection_name(step): + name_css = 'input.new-subsection-name-input' + save_css = 'input.new-subsection-name-save' + css_fill(name_css,'Subsection One') + css_click(save_css) + +@step('I have added a new subsection$') +def i_have_added_a_new_subsection(step): + add_subsection() + +############ ASSERTIONS ################### +@step('I see my subsection on the Courseware page$') +def i_see_my_subsection_on_the_courseware_page(step): + css = 'span.subsection-name' + assert world.browser.is_element_present_by_css(css) + css = 'span.subsection-name-value' + assert_css_with_text(css,'Subsection One') + +@step('the subsection does not exist$') +def the_subsection_does_not_exist(step): + css = 'span.subsection-name' + assert world.browser.is_element_not_present_by_css(css) \ No newline at end of file diff --git a/cms/djangoapps/contentstore/tests/test_course_updates.py b/cms/djangoapps/contentstore/tests/test_course_updates.py new file mode 100644 index 0000000000..b05754d214 --- /dev/null +++ b/cms/djangoapps/contentstore/tests/test_course_updates.py @@ -0,0 +1,29 @@ +from cms.djangoapps.contentstore.tests.test_course_settings import CourseTestCase +from django.core.urlresolvers import reverse +import json +from cms.djangoapps.contentstore.course_info_model import update_course_updates + + +class CourseUpdateTest(CourseTestCase): + def test_course_update(self): + # first get the update to force the creation + url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, + 'name' : self.course_location.name }) + self.client.get(url) + + content = '' + payload = { 'content' : content, + 'date' : 'January 8, 2013'} + # No means to post w/ provided_id missing. django doesn't handle. So, go direct for the create + payload = update_course_updates(['i4x', self.course_location.org, self.course_location.course, 'course_info', "updates"] , payload) + + url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course, + 'provided_id' : payload['id']}) + + self.assertHTMLEqual(content, payload['content'], "single iframe") + + content += '
                  div

                  p

                  ' + payload['content'] = content + resp = self.client.post(url, json.dumps(payload), "application/json") + + self.assertHTMLEqual(content, json.loads(resp.content)['content'], "iframe w/ div") diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 19d74024c7..86d821cbb6 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -200,7 +200,7 @@ def edit_subsection(request, location): # make sure that location references a 'sequential', otherwise return BadRequest if item.location.category != 'sequential': - return HttpResponseBadRequest + return HttpResponseBadRequest() parent_locs = modulestore().get_parent_locations(location) @@ -993,7 +993,7 @@ def course_info_updates(request, org, course, provided_id=None): elif request.method == 'POST': try: return HttpResponse(json.dumps(update_course_updates(location, request.POST, provided_id)), mimetype="application/json") - except etree.XMLSyntaxError: + except: return HttpResponseBadRequest("Failed to save: malformed html", content_type="text/plain") @@ -1025,7 +1025,7 @@ def module_info(request, module_location): elif real_method == 'POST' or real_method == 'PUT': return HttpResponse(json.dumps(set_module_info(get_modulestore(location), location, request.POST)), mimetype="application/json") else: - return HttpResponseBadRequest + return HttpResponseBadRequest() @login_required @ensure_csrf_cookie diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py new file mode 100644 index 0000000000..5bc9b53fc4 --- /dev/null +++ b/cms/envs/acceptance.py @@ -0,0 +1,38 @@ +""" +This config file extends the test environment configuration +so that we can run the lettuce acceptance tests. +""" +from .test import * + +# You need to start the server in debug mode, +# otherwise the browser will not render the pages correctly +DEBUG = True + +# Show the courses that are in the data directory +COURSES_ROOT = ENV_ROOT / "data" +DATA_DIR = COURSES_ROOT +# MODULESTORE = { +# 'default': { +# 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', +# 'OPTIONS': { +# 'data_dir': DATA_DIR, +# 'default_class': 'xmodule.hidden_module.HiddenDescriptor', +# } +# } +# } + +# Set this up so that rake lms[acceptance] and running the +# harvest command both use the same (test) database +# which they can flush without messing up your dev db +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ENV_ROOT / "db" / "test_mitx.db", + 'TEST_NAME': ENV_ROOT / "db" / "test_mitx.db", + } +} + +# Include the lettuce app for acceptance testing, including the 'harvest' django-admin command +INSTALLED_APPS += ('lettuce.django',) +LETTUCE_APPS = ('contentstore',) +LETTUCE_SERVER_PORT = 8001 diff --git a/cms/envs/common.py b/cms/envs/common.py index 4b4b69ad39..c047d689ce 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -34,6 +34,7 @@ MITX_FEATURES = { 'GITHUB_PUSH': False, 'ENABLE_DISCUSSION_SERVICE': False, 'AUTH_USE_MIT_CERTIFICATES' : False, + 'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests } ENABLE_JASMINE = False diff --git a/cms/static/js/speed-editor.js b/cms/static/js/speed-editor.js deleted file mode 100644 index 9ff7c0248e..0000000000 --- a/cms/static/js/speed-editor.js +++ /dev/null @@ -1,434 +0,0 @@ -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, -// TODO: I left out the extra keys for now. - 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(); - -// TODO: is this necessary?? - $(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\=\=+$)/gm, '

                  $1

                  '); - xml = xml.replace(/\n^\=\=+$/gm, ''); - - // group multiple choice answers - xml = xml.replace(/(^\s*\(.?\).*?$\n*)+/gm, 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*\(.?\)\s*/)[1]; - var correct = /^\s*\(x\)/i.test(options[i]); - groupString += ' ' + value + '\n'; - } - } - groupString += ' \n'; - groupString += '\n\n'; - return groupString; - }); - - // group check answers - xml = xml.replace(/(^\s*\[.?\].*?$\n*)+/gm, 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*\[.?\]\s*/)[1]; - var correct = /^\s*\[x\]/i.test(options[i]); - groupString += ' ' + value + '\n'; - } - } - groupString += ' \n'; - groupString += '\n\n'; - return groupString; - }); - - // replace videos - xml = xml.replace(/\{\{video\s(.*?)\}\}/g, '