Fix merge conflict.

This commit is contained in:
Peter Fogg
2013-06-03 08:49:52 -04:00
141 changed files with 41701 additions and 966 deletions

View File

@@ -27,3 +27,59 @@ def click_component_from_menu(instance_id, expected_css):
assert_equal(1, len(world.css_find(elem_css)))
world.css_click(elem_css)
assert_equal(1, len(world.css_find(expected_css)))
@world.absorb
def edit_component_and_select_settings():
world.css_click('a.edit-button')
world.css_click('#settings-mode')
@world.absorb
def verify_setting_entry(setting, display_name, value, explicitly_set):
assert_equal(display_name, setting.find_by_css('.setting-label')[0].value)
assert_equal(value, setting.find_by_css('.setting-input')[0].value)
settingClearButton = setting.find_by_css('.setting-clear')[0]
assert_equal(explicitly_set, settingClearButton.has_class('active'))
assert_equal(not explicitly_set, settingClearButton.has_class('inactive'))
@world.absorb
def verify_all_setting_entries(expected_entries):
settings = world.browser.find_by_css('.wrapper-comp-setting')
assert_equal(len(expected_entries), len(settings))
for (counter, setting) in enumerate(settings):
world.verify_setting_entry(
setting, expected_entries[counter][0],
expected_entries[counter][1], expected_entries[counter][2]
)
@world.absorb
def save_component_and_reopen(step):
world.css_click("a.save-button")
# We have a known issue that modifications are still shown within the edit window after cancel (though)
# they are not persisted. Refresh the browser to make sure the changes WERE persisted after Save.
reload_the_page(step)
edit_component_and_select_settings()
@world.absorb
def cancel_component(step):
world.css_click("a.cancel-button")
# We have a known issue that modifications are still shown within the edit window after cancel (though)
# they are not persisted. Refresh the browser to make sure the changes were not persisted.
reload_the_page(step)
@world.absorb
def revert_setting_entry(label):
get_setting_entry(label).find_by_css('.setting-clear')[0].click()
@world.absorb
def get_setting_entry(label):
settings = world.browser.find_by_css('.wrapper-comp-setting')
for setting in settings:
if setting.find_by_css('.setting-label')[0].value == label:
return setting
return None

View File

@@ -0,0 +1,13 @@
Feature: Discussion Component Editor
As a course author, I want to be able to create discussion components.
Scenario: User can view metadata
Given I have created a Discussion Tag
And I edit and select Settings
Then I see three alphabetized settings and their expected values
Scenario: User can modify display name
Given I have created a Discussion Tag
And I edit and select Settings
Then I can modify the display name
And my display name change is persisted on save

View File

@@ -0,0 +1,23 @@
# disable missing docstring
#pylint: disable=C0111
from lettuce import world, step
@step('I have created a Discussion Tag$')
def i_created_discussion_tag(step):
world.create_component_instance(
step, '.large-discussion-icon',
'i4x://edx/templates/discussion/Discussion_Tag',
'.xmodule_DiscussionModule'
)
@step('I see three alphabetized settings and their expected values$')
def i_see_only_the_settings_and_values(step):
world.verify_all_setting_entries(
[
['Category', "Week 1", True],
['Display Name', "Discussion Tag", True],
['Subcategory', "Topic-Level Student-Visible Label", True]
])

View File

@@ -0,0 +1,13 @@
Feature: HTML Editor
As a course author, I want to be able to create HTML blocks.
Scenario: User can view metadata
Given I have created a Blank HTML Page
And I edit and select Settings
Then I see only the HTML display name setting
Scenario: User can modify display name
Given I have created a Blank HTML Page
And I edit and select Settings
Then I can modify the display name
And my display name change is persisted on save

View File

@@ -0,0 +1,17 @@
# disable missing docstring
#pylint: disable=C0111
from lettuce import world, step
@step('I have created a Blank HTML Page$')
def i_created_blank_html_page(step):
world.create_component_instance(
step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page',
'.xmodule_HtmlModule'
)
@step('I see only the HTML display name setting$')
def i_see_only_the_html_display_name(step):
world.verify_all_setting_entries([['Display Name', "Blank HTML Page", True]])

View File

@@ -0,0 +1,67 @@
Feature: Problem Editor
As a course author, I want to be able to create problems and edit their settings.
Scenario: User can view metadata
Given I have created a Blank Common Problem
And I edit and select Settings
Then I see five alphabetized settings and their expected values
And Edit High Level Source is not visible
Scenario: User can modify String values
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can modify the display name
And my display name change is persisted on save
Scenario: User can specify special characters in String values
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can specify special characters in the display name
And my special characters and persisted on save
Scenario: User can revert display name to unset
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can revert the display name to unset
And my display name is unset on save
Scenario: User can select values in a Select
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can select Per Student for Randomization
And my change to randomization is persisted
And I can revert to the default value for randomization
Scenario: User can modify float input values
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can set the weight to "3.5"
And my change to weight is persisted
And I can revert to the default value of unset for weight
Scenario: User cannot type letters in float number field
Given I have created a Blank Common Problem
And I edit and select Settings
Then if I set the weight to "abc", it remains unset
Scenario: User cannot type decimal values integer number field
Given I have created a Blank Common Problem
And I edit and select Settings
Then if I set the max attempts to "2.34", it displays initially as "234", and is persisted as "234"
Scenario: User cannot type out of range values in an integer number field
Given I have created a Blank Common Problem
And I edit and select Settings
Then if I set the max attempts to "-3", it displays initially as "-3", and is persisted as "1"
Scenario: Settings changes are not saved on Cancel
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can set the weight to "3.5"
And I can modify the display name
Then If I press Cancel my changes are not persisted
Scenario: Edit High Level source is available for LaTeX problem
Given I have created a LaTeX Problem
And I edit and select Settings
Then Edit High Level Source is visible

View File

@@ -0,0 +1,187 @@
# disable missing docstring
#pylint: disable=C0111
from lettuce import world, step
from nose.tools import assert_equal
DISPLAY_NAME = "Display Name"
MAXIMUM_ATTEMPTS = "Maximum Attempts"
PROBLEM_WEIGHT = "Problem Weight"
RANDOMIZATION = 'Randomization'
SHOW_ANSWER = "Show Answer"
############### ACTIONS ####################
@step('I have created a Blank Common Problem$')
def i_created_blank_common_problem(step):
world.create_component_instance(
step,
'.large-problem-icon',
'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule'
)
@step('I edit and select Settings$')
def i_edit_and_select_settings(step):
world.edit_component_and_select_settings()
@step('I see five alphabetized settings and their expected values$')
def i_see_five_settings_with_values(step):
world.verify_all_setting_entries(
[
[DISPLAY_NAME, "Blank Common Problem", True],
[MAXIMUM_ATTEMPTS, "", False],
[PROBLEM_WEIGHT, "", False],
[RANDOMIZATION, "Never", True],
[SHOW_ANSWER, "Finished", True]
])
@step('I can modify the display name')
def i_can_modify_the_display_name(step):
world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill('modified')
verify_modified_display_name()
@step('my display name change is persisted on save')
def my_display_name_change_is_persisted_on_save(step):
world.save_component_and_reopen(step)
verify_modified_display_name()
@step('I can specify special characters in the display name')
def i_can_modify_the_display_name_with_special_chars(step):
world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill("updated ' \" &")
verify_modified_display_name_with_special_chars()
@step('my special characters and persisted on save')
def special_chars_persisted_on_save(step):
world.save_component_and_reopen(step)
verify_modified_display_name_with_special_chars()
@step('I can revert the display name to unset')
def can_revert_display_name_to_unset(step):
world.revert_setting_entry(DISPLAY_NAME)
verify_unset_display_name()
@step('my display name is unset on save')
def my_display_name_is_persisted_on_save(step):
world.save_component_and_reopen(step)
verify_unset_display_name()
@step('I can select Per Student for Randomization')
def i_can_select_per_student_for_randomization(step):
world.browser.select(RANDOMIZATION, "Per Student")
verify_modified_randomization()
@step('my change to randomization is persisted')
def my_change_to_randomization_is_persisted(step):
world.save_component_and_reopen(step)
verify_modified_randomization()
@step('I can revert to the default value for randomization')
def i_can_revert_to_default_for_randomization(step):
world.revert_setting_entry(RANDOMIZATION)
world.save_component_and_reopen(step)
world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Always", False)
@step('I can set the weight to "(.*)"?')
def i_can_set_weight(step, weight):
set_weight(weight)
verify_modified_weight()
@step('my change to weight is persisted')
def my_change_to_weight_is_persisted(step):
world.save_component_and_reopen(step)
verify_modified_weight()
@step('I can revert to the default value of unset for weight')
def i_can_revert_to_default_for_unset_weight(step):
world.revert_setting_entry(PROBLEM_WEIGHT)
world.save_component_and_reopen(step)
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False)
@step('if I set the weight to "(.*)", it remains unset')
def set_the_weight_to_abc(step, bad_weight):
set_weight(bad_weight)
# We show the clear button immediately on type, hence the "True" here.
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", True)
world.save_component_and_reopen(step)
# But no change was actually ever sent to the model, so on reopen, explicitly_set is False
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False)
@step('if I set the max attempts to "(.*)", it displays initially as "(.*)", and is persisted as "(.*)"')
def set_the_max_attempts(step, max_attempts_set, max_attempts_displayed, max_attempts_persisted):
world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill(max_attempts_set)
world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, max_attempts_displayed, True)
world.save_component_and_reopen(step)
world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, max_attempts_persisted, True)
@step('Edit High Level Source is not visible')
def edit_high_level_source_not_visible(step):
verify_high_level_source(step, False)
@step('Edit High Level Source is visible')
def edit_high_level_source_visible(step):
verify_high_level_source(step, True)
@step('If I press Cancel my changes are not persisted')
def cancel_does_not_save_changes(step):
world.cancel_component(step)
step.given("I edit and select Settings")
step.given("I see five alphabetized settings and their expected values")
@step('I have created a LaTeX Problem')
def create_latex_problem(step):
world.click_new_component_button(step, '.large-problem-icon')
# Go to advanced tab (waiting for the tab to be visible)
world.css_find('#ui-id-2')
world.css_click('#ui-id-2')
world.click_component_from_menu("i4x://edx/templates/problem/Problem_Written_in_LaTeX", '.xmodule_CapaModule')
def verify_high_level_source(step, visible):
assert_equal(visible, world.is_css_present('.launch-latex-compiler'))
world.cancel_component(step)
assert_equal(visible, world.is_css_present('.upload-button'))
def verify_modified_weight():
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "3.5", True)
def verify_modified_randomization():
world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Per Student", True)
def verify_modified_display_name():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'modified', True)
def verify_modified_display_name_with_special_chars():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, "updated ' \" &", True)
def verify_unset_display_name():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '', False)
def set_weight(weight):
world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill(weight)

View File

@@ -10,9 +10,7 @@ from nose.tools import assert_equal
@step('I have opened a new course section in Studio$')
def i_have_opened_a_new_course_section(step):
world.clear_courses()
log_into_studio()
create_a_course()
open_new_course()
add_section()

View File

@@ -0,0 +1,13 @@
Feature: Video Component Editor
As a course author, I want to be able to create video components.
Scenario: User can view metadata
Given I have created a Video component
And I edit and select Settings
Then I see only the Video display name setting
Scenario: User can modify display name
Given I have created a Video component
And I edit and select Settings
Then I can modify the display name
And my display name change is persisted on save

View File

@@ -0,0 +1,9 @@
# disable missing docstring
#pylint: disable=C0111
from lettuce import world, step
@step('I see only the video display name setting$')
def i_see_only_the_video_display_name(step):
world.verify_all_setting_entries([['Display Name', "default", True]])

View File

@@ -34,6 +34,8 @@ from xmodule.course_module import CourseDescriptor
from xmodule.seq_module import SequenceDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError
from contentstore.views.component import ADVANCED_COMPONENT_TYPES
from django_comment_common.utils import are_permissions_roles_seeded
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
@@ -75,6 +77,31 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.client = Client()
self.client.login(username=uname, password=password)
def test_advanced_components_in_edit_unit(self):
store = modulestore('direct')
import_from_xml(store, 'common/test/data/', ['simple'])
course = store.get_item(Location(['i4x', 'edX', 'simple',
'course', '2012_Fall', None]), depth=None)
course.advanced_modules = ADVANCED_COMPONENT_TYPES
store.update_metadata(course.location, own_metadata(course))
# just pick one vertical
descriptor = store.get_items(Location('i4x', 'edX', 'simple', 'vertical', None, None))[0]
resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
self.assertEqual(resp.status_code, 200)
# This could be made better, but for now let's just assert that we see the advanced modules mentioned in the page
# response HTML
self.assertIn('Video Alpha', resp.content)
self.assertIn('Word cloud', resp.content)
self.assertIn('Annotation', resp.content)
self.assertIn('Open Ended Response', resp.content)
self.assertIn('Peer Grading Interface', resp.content)
def check_edit_unit(self, test_course_name):
import_from_xml(modulestore('direct'), 'common/test/data/', [test_course_name])

View File

@@ -111,6 +111,18 @@ class AuthTestCase(ContentStoreTestCase):
# Now login should work
self.login(self.email, self.pw)
def test_login_link_on_activation_age(self):
self.create_account(self.username, self.email, self.pw)
# we want to test the rendering of the activation page when the user isn't logged in
self.client.logout()
resp = self._activate_user(self.email)
self.assertEqual(resp.status_code, 200)
# check the the HTML has links to the right login page. Note that this is merely a content
# check and thus could be fragile should the wording change on this page
expected = 'You can now <a href="' + reverse('login') + '">login</a>.'
self.assertIn(expected, resp.content)
def test_private_pages_auth(self):
"""Make sure pages that do require login work."""
auth_pages = (

View File

@@ -42,7 +42,7 @@ COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"]
NOTE_COMPONENT_TYPES = ['notes']
ADVANCED_COMPONENT_TYPES = ['annotatable', 'word_cloud'] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
ADVANCED_COMPONENT_TYPES = ['annotatable', 'word_cloud', 'videoalpha'] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
ADVANCED_COMPONENT_CATEGORY = 'advanced'
ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
@@ -149,8 +149,7 @@ def edit_unit(request, location):
component_templates[category].append((
template.display_name_with_default,
template.location.url(),
hasattr(template, 'markdown') and template.markdown is not None,
template.cms.empty,
hasattr(template, 'markdown') and template.markdown is not None
))
components = [

View File

@@ -226,7 +226,8 @@ PIPELINE_JS = {
rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.js')
) + ['js/hesitate.js', 'js/base.js',
'js/models/feedback.js', 'js/views/feedback.js',
'js/models/section.js', 'js/views/section.js'],
'js/models/section.js', 'js/views/section.js',
'js/models/metadata_model.js', 'js/views/metadata_editor_view.js'],
'output_filename': 'js/cms-application.js',
'test_order': 0
},

View File

@@ -0,0 +1 @@
../../../templates/js/metadata-editor.underscore

View File

@@ -0,0 +1 @@
../../../templates/js/metadata-number-entry.underscore

View File

@@ -0,0 +1 @@
../../../templates/js/metadata-option-entry.underscore

View File

@@ -0,0 +1 @@
../../../templates/js/metadata-string-entry.underscore

View File

@@ -0,0 +1,58 @@
describe "CMS.Models.Metadata", ->
it "knows when the value has not been modified", ->
model = new CMS.Models.Metadata(
{'value': 'original', 'explicitly_set': false})
expect(model.isModified()).toBeFalsy()
model = new CMS.Models.Metadata(
{'value': 'original', 'explicitly_set': true})
model.setValue('original')
expect(model.isModified()).toBeFalsy()
it "knows when the value has been modified", ->
model = new CMS.Models.Metadata(
{'value': 'original', 'explicitly_set': false})
model.setValue('original')
expect(model.isModified()).toBeTruthy()
model = new CMS.Models.Metadata(
{'value': 'original', 'explicitly_set': true})
model.setValue('modified')
expect(model.isModified()).toBeTruthy()
it "tracks when values have been explicitly set", ->
model = new CMS.Models.Metadata(
{'value': 'original', 'explicitly_set': false})
expect(model.isExplicitlySet()).toBeFalsy()
model.setValue('original')
expect(model.isExplicitlySet()).toBeTruthy()
it "has both 'display value' and a 'value' methods", ->
model = new CMS.Models.Metadata(
{'value': 'default', 'explicitly_set': false})
expect(model.getValue()).toBeNull
expect(model.getDisplayValue()).toBe('default')
model.setValue('modified')
expect(model.getValue()).toBe('modified')
expect(model.getDisplayValue()).toBe('modified')
it "has a clear method for reverting to the default", ->
model = new CMS.Models.Metadata(
{'value': 'original', 'default_value' : 'default', 'explicitly_set': true})
model.clear()
expect(model.getValue()).toBeNull
expect(model.getDisplayValue()).toBe('default')
expect(model.isExplicitlySet()).toBeFalsy()
it "has a getter for field name", ->
model = new CMS.Models.Metadata({'field_name': 'foo'})
expect(model.getFieldName()).toBe('foo')
it "has a getter for options", ->
model = new CMS.Models.Metadata({'options': ['foo', 'bar']})
expect(model.getOptions()).toEqual(['foo', 'bar'])
it "has a getter for type", ->
model = new CMS.Models.Metadata({'type': 'Integer'})
expect(model.getType()).toBe(CMS.Models.Metadata.INTEGER_TYPE)

View File

@@ -0,0 +1,300 @@
describe "Test Metadata Editor", ->
editorTemplate = readFixtures('metadata-editor.underscore')
numberEntryTemplate = readFixtures('metadata-number-entry.underscore')
stringEntryTemplate = readFixtures('metadata-string-entry.underscore')
optionEntryTemplate = readFixtures('metadata-option-entry.underscore')
beforeEach ->
setFixtures($("<script>", {id: "metadata-editor-tpl", type: "text/template"}).text(editorTemplate))
appendSetFixtures($("<script>", {id: "metadata-number-entry", type: "text/template"}).text(numberEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-option-entry", type: "text/template"}).text(optionEntryTemplate))
genericEntry = {
default_value: 'default value',
display_name: "Display Name",
explicitly_set: true,
field_name: "display_name",
help: "Specifies the name for this component.",
inheritable: false,
options: [],
type: CMS.Models.Metadata.GENERIC_TYPE,
value: "Word cloud"
}
selectEntry = {
default_value: "answered",
display_name: "Show Answer",
explicitly_set: false,
field_name: "show_answer",
help: "When should you show the answer",
inheritable: true,
options: [
{"display_name": "Always", "value": "always"},
{"display_name": "Answered", "value": "answered"},
{"display_name": "Never", "value": "never"}
],
type: CMS.Models.Metadata.SELECT_TYPE,
value: "always"
}
integerEntry = {
default_value: 5,
display_name: "Inputs",
explicitly_set: false,
field_name: "num_inputs",
help: "Number of text boxes for student to input words/sentences.",
inheritable: false,
options: {min: 1},
type: CMS.Models.Metadata.INTEGER_TYPE,
value: 5
}
floatEntry = {
default_value: 2.7,
display_name: "Weight",
explicitly_set: true,
field_name: "weight",
help: "Weight for this problem",
inheritable: true,
options: {min: 1.3, max:100.2, step:0.1},
type: CMS.Models.Metadata.FLOAT_TYPE,
value: 10.2
}
# Test for the editor that creates the individual views.
describe "CMS.Views.Metadata.Editor creates editors for each field", ->
beforeEach ->
@model = new CMS.Models.MetadataCollection(
[
integerEntry,
floatEntry,
selectEntry,
genericEntry,
{
default_value: null,
display_name: "Unknown",
explicitly_set: true,
field_name: "unknown_type",
help: "Mystery property.",
inheritable: false,
options: [
{"display_name": "Always", "value": "always"},
{"display_name": "Answered", "value": "answered"},
{"display_name": "Never", "value": "never"}],
type: "unknown type",
value: null
}
]
)
it "creates child views on initialize, and sorts them alphabetically", ->
view = new CMS.Views.Metadata.Editor({collection: @model})
childModels = view.collection.models
expect(childModels.length).toBe(5)
childViews = view.$el.find('.setting-input')
expect(childViews.length).toBe(5)
verifyEntry = (index, display_name, type) ->
expect(childModels[index].get('display_name')).toBe(display_name)
expect(childViews[index].type).toBe(type)
verifyEntry(0, 'Display Name', 'text')
verifyEntry(1, 'Inputs', 'number')
verifyEntry(2, 'Show Answer', 'select-one')
verifyEntry(3, 'Unknown', 'text')
verifyEntry(4, 'Weight', 'number')
it "returns its display name", ->
view = new CMS.Views.Metadata.Editor({collection: @model})
expect(view.getDisplayName()).toBe("Word cloud")
it "returns an empty string if there is no display name property with a valid value", ->
view = new CMS.Views.Metadata.Editor({collection: new CMS.Models.MetadataCollection()})
expect(view.getDisplayName()).toBe("")
view = new CMS.Views.Metadata.Editor({collection: new CMS.Models.MetadataCollection([
{
default_value: null,
display_name: "Display Name",
explicitly_set: false,
field_name: "display_name",
help: "",
inheritable: false,
options: [],
type: CMS.Models.Metadata.GENERIC_TYPE,
value: null
}])
})
expect(view.getDisplayName()).toBe("")
it "has no modified values by default", ->
view = new CMS.Views.Metadata.Editor({collection: @model})
expect(view.getModifiedMetadataValues()).toEqual({})
it "returns modified values only", ->
view = new CMS.Views.Metadata.Editor({collection: @model})
childModels = view.collection.models
childModels[0].setValue('updated display name')
childModels[1].setValue(20)
expect(view.getModifiedMetadataValues()).toEqual({
display_name : 'updated display name',
num_inputs: 20
})
# Tests for individual views.
assertInputType = (view, expectedType) ->
input = view.$el.find('.setting-input')
expect(input.length).toBe(1)
expect(input[0].type).toBe(expectedType)
assertValueInView = (view, expectedValue) ->
expect(view.getValueFromEditor()).toBe(expectedValue)
assertCanUpdateView = (view, newValue) ->
view.setValueInEditor(newValue)
expect(view.getValueFromEditor()).toBe(newValue)
assertClear = (view, modelValue, editorValue=modelValue) ->
view.clear()
expect(view.model.getValue()).toBe(null)
expect(view.model.getDisplayValue()).toBe(modelValue)
expect(view.getValueFromEditor()).toBe(editorValue)
assertUpdateModel = (view, originalValue, newValue) ->
view.setValueInEditor(newValue)
expect(view.model.getValue()).toBe(originalValue)
view.updateModel()
expect(view.model.getValue()).toBe(newValue)
describe "CMS.Views.Metadata.String is a basic string input with clear functionality", ->
beforeEach ->
model = new CMS.Models.Metadata(genericEntry)
@view = new CMS.Views.Metadata.String({model: model})
it "uses a text input type", ->
assertInputType(@view, 'text')
it "returns the intial value upon initialization", ->
assertValueInView(@view, 'Word cloud')
it "can update its value in the view", ->
assertCanUpdateView(@view, "updated ' \" &")
it "has a clear method to revert to the model default", ->
assertClear(@view, 'default value')
it "has an update model method", ->
assertUpdateModel(@view, 'Word cloud', 'updated')
describe "CMS.Views.Metadata.Option is an option input type with clear functionality", ->
beforeEach ->
model = new CMS.Models.Metadata(selectEntry)
@view = new CMS.Views.Metadata.Option({model: model})
it "uses a select input type", ->
assertInputType(@view, 'select-one')
it "returns the intial value upon initialization", ->
assertValueInView(@view, 'always')
it "can update its value in the view", ->
assertCanUpdateView(@view, "never")
it "has a clear method to revert to the model default", ->
assertClear(@view, 'answered')
it "has an update model method", ->
assertUpdateModel(@view, null, 'never')
it "does not update to a value that is not an option", ->
@view.setValueInEditor("not an option")
expect(@view.getValueFromEditor()).toBe('always')
describe "CMS.Views.Metadata.Number supports integer or float type and has clear functionality", ->
beforeEach ->
integerModel = new CMS.Models.Metadata(integerEntry)
@integerView = new CMS.Views.Metadata.Number({model: integerModel})
floatModel = new CMS.Models.Metadata(floatEntry)
@floatView = new CMS.Views.Metadata.Number({model: floatModel})
it "uses a number input type", ->
assertInputType(@integerView, 'number')
assertInputType(@floatView, 'number')
it "returns the intial value upon initialization", ->
assertValueInView(@integerView, '5')
assertValueInView(@floatView, '10.2')
it "can update its value in the view", ->
assertCanUpdateView(@integerView, "12")
assertCanUpdateView(@floatView, "-2.4")
it "has a clear method to revert to the model default", ->
assertClear(@integerView, 5, '5')
assertClear(@floatView, 2.7, '2.7')
it "has an update model method", ->
assertUpdateModel(@integerView, null, '90')
assertUpdateModel(@floatView, 10.2, '-9.5')
it "knows the difference between integer and float", ->
expect(@integerView.isIntegerField()).toBeTruthy()
expect(@floatView.isIntegerField()).toBeFalsy()
it "sets attribtues related to min, max, and step", ->
verifyAttributes = (view, min, step, max=null) ->
inputEntry = view.$el.find('input')
expect(Number(inputEntry.attr('min'))).toEqual(min)
expect(Number(inputEntry.attr('step'))).toEqual(step)
if max is not null
expect(Number(inputEntry.attr('max'))).toEqual(max)
verifyAttributes(@integerView, 1, 1)
verifyAttributes(@floatView, 1.3, .1, 100.2)
it "corrects values that are out of range", ->
verifyValueAfterChanged = (view, value, expectedResult) ->
view.setValueInEditor(value)
view.changed()
expect(view.getValueFromEditor()).toBe(expectedResult)
verifyValueAfterChanged(@integerView, '-4', '1')
verifyValueAfterChanged(@integerView, '1', '1')
verifyValueAfterChanged(@integerView, '0', '1')
verifyValueAfterChanged(@integerView, '3001', '3001')
verifyValueAfterChanged(@floatView, '-4', '1.3')
verifyValueAfterChanged(@floatView, '1.3', '1.3')
verifyValueAfterChanged(@floatView, '1.2', '1.3')
verifyValueAfterChanged(@floatView, '100.2', '100.2')
verifyValueAfterChanged(@floatView, '100.3', '100.2')
it "disallows invalid characters", ->
verifyValueAfterKeyPressed = (view, character, reject) ->
event = {
type : 'keypress',
which : character.charCodeAt(0),
keyCode: character.charCodeAt(0),
preventDefault : () -> 'no op'
}
spyOn(event, 'preventDefault')
view.$el.find('input').trigger(event)
if (reject)
expect(event.preventDefault).toHaveBeenCalled()
else
expect(event.preventDefault).not.toHaveBeenCalled()
verifyDisallowedChars = (view) ->
verifyValueAfterKeyPressed(view, 'a', true)
verifyValueAfterKeyPressed(view, '.', view.isIntegerField())
verifyValueAfterKeyPressed(view, '[', true)
verifyValueAfterKeyPressed(view, '@', true)
for i in [0...9]
verifyValueAfterKeyPressed(view, String(i), false)
verifyDisallowedChars(@integerView)
verifyDisallowedChars(@floatView)

View File

@@ -73,13 +73,3 @@ describe "CMS.Views.ModuleEdit", ->
expect(XModule.loadModule).toHaveBeenCalled()
expect(XModule.loadModule.mostRecentCall.args[0]).toBe($('.xmodule_display'))
describe "changedMetadata", ->
it "returns empty if no metadata loaded", ->
expect(@moduleEdit.changedMetadata()).toEqual({})
it "returns only changed values", ->
@moduleEdit.originalMetadata = {'foo', 'bar'}
spyOn(@moduleEdit, 'metadata').andReturn({'a': '', 'b': 'before', 'c': ''})
@moduleEdit.loadEdit()
@moduleEdit.metadata.andReturn({'a': '', 'b': 'after', 'd': 'only_after'})
expect(@moduleEdit.changedMetadata()).toEqual({'b' : 'after', 'd' : 'only_after'})

View File

@@ -1,12 +1,14 @@
class CMS.Views.ModuleEdit extends Backbone.View
tagName: 'li'
className: 'component'
editorMode: 'editor-mode'
events:
"click .component-editor .cancel-button": 'clickCancelButton'
"click .component-editor .save-button": 'clickSaveButton'
"click .component-actions .edit-button": 'clickEditButton'
"click .component-actions .delete-button": 'onDelete'
"click .mode a": 'clickModeButton'
initialize: ->
@onDelete = @options.onDelete
@@ -20,29 +22,30 @@ class CMS.Views.ModuleEdit extends Backbone.View
loadEdit: ->
if not @module
@module = XModule.loadModule(@$el.find('.xmodule_edit'))
@originalMetadata = @metadata()
# At this point, metadata-edit.html will be loaded, and the metadata (as JSON) is available.
metadataEditor = @$el.find('.metadata_edit')
metadataData = metadataEditor.data('metadata')
models = [];
for key of metadataData
models.push(metadataData[key])
@metadataEditor = new CMS.Views.Metadata.Editor({
el: metadataEditor,
collection: new CMS.Models.MetadataCollection(models)
})
metadata: ->
# cdodge: package up metadata which is separated into a number of input fields
# there's probably a better way to do this, but at least this lets me continue to move onwards
_metadata = {}
# Need to update set "active" class on data editor if there is one.
# If we are only showing settings, hide the data editor controls and update settings accordingly.
if @hasDataEditor()
@selectMode(@editorMode)
else
@hideDataEditor()
$metadata = @$component_editor().find('.metadata_edit')
if $metadata
# walk through the set of elments which have the 'xmetadata_name' attribute and
# build up a object to pass back to the server on the subsequent POST
_metadata[$(el).data("metadata-name")] = el.value for el in $('[data-metadata-name]', $metadata)
return _metadata
title = interpolate(gettext('<em>Editing:</em> %s'),
[@metadataEditor.getDisplayName()])
@$el.find('.component-name').html(title)
changedMetadata: ->
currentMetadata = @metadata()
changedMetadata = {}
for key of currentMetadata
if currentMetadata[key] != @originalMetadata[key]
changedMetadata[key] = currentMetadata[key]
return changedMetadata
return @metadataEditor.getModifiedMetadataValues()
cloneTemplate: (parent, template) ->
$.post("/clone_item", {
@@ -77,7 +80,7 @@ class CMS.Views.ModuleEdit extends Backbone.View
@render()
@$el.removeClass('editing')
).fail( ->
showToastMessage("There was an error saving your changes. Please try again.", null, 3)
showToastMessage(gettext("There was an error saving your changes. Please try again."), null, 3)
)
clickCancelButton: (event) ->
@@ -96,3 +99,38 @@ class CMS.Views.ModuleEdit extends Backbone.View
$modalCover.show().addClass('is-fixed')
@$component_editor().slideDown(150)
@loadEdit()
clickModeButton: (event) ->
event.preventDefault()
if not @hasDataEditor()
return
@selectMode(event.currentTarget.parentElement.id)
hasDataEditor: =>
return @$el.find('.wrapper-comp-editor').length > 0
selectMode: (mode) =>
dataEditor = @$el.find('.wrapper-comp-editor')
settingsEditor = @$el.find('.wrapper-comp-settings')
editorModeButton = @$el.find('#editor-mode').find("a")
settingsModeButton = @$el.find('#settings-mode').find("a")
if mode == @editorMode
# Because of CodeMirror editor, cannot hide the data editor when it is first loaded. Therefore
# we have to use a class of is-inactive instead of is-active.
dataEditor.removeClass('is-inactive')
editorModeButton.addClass('is-set')
settingsEditor.removeClass('is-active')
settingsModeButton.removeClass('is-set')
else
dataEditor.addClass('is-inactive')
editorModeButton.removeClass('is-set')
settingsEditor.addClass('is-active')
settingsModeButton.addClass('is-set')
hideDataEditor: =>
editorModeButtonParent = @$el.find('#editor-mode')
editorModeButtonParent.addClass('inactive-mode')
editorModeButtonParent.removeClass('active-mode')
@$el.find('.wrapper-comp-settings').addClass('is-active')
@$el.find('#settings-mode').find("a").addClass('is-set')

View File

@@ -0,0 +1,113 @@
/**
* Model used for metadata setting editors. This model does not do its own saving,
* as that is done by module_edit.coffee.
*/
CMS.Models.Metadata = Backbone.Model.extend({
defaults: {
"field_name": null,
"display_name": null,
"value" : null,
"explicitly_set": null,
"default_value" : null,
"options" : null,
"type" : null
},
initialize: function() {
this.original_value = this.get('value');
this.original_explicitly_set = this.get('explicitly_set');
},
/**
* Returns true if the stored value is different, or if the "explicitly_set"
* property has changed.
*/
isModified : function() {
if (!this.get('explicitly_set') && !this.original_explicitly_set) {
return false;
}
if (this.get('explicitly_set') && this.original_explicitly_set) {
return this.get('value') !== this.original_value;
}
return true;
},
/**
* Returns true if a non-default/non-inherited value has been set.
*/
isExplicitlySet: function() {
return this.get('explicitly_set');
},
/**
* The value, as shown in the UI. This may be an inherited or default value.
*/
getDisplayValue : function () {
return this.get('value');
},
/**
* The value, as should be returned to the server. if 'isExplicitlySet'
* returns false, this method returns null to indicate that the value
* is not set at this level.
*/
getValue: function() {
return this.get('explicitly_set') ? this.get('value') : null;
},
/**
* Sets the displayed value.
*/
setValue: function (value) {
this.set({
explicitly_set: true,
value: value
});
},
/**
* Returns the field name, which should be used for persisting the metadata
* field to the server.
*/
getFieldName: function () {
return this.get('field_name');
},
/**
* Returns the options. This may be a array of possible values, or an object
* with properties like "max", "min" and "step".
*/
getOptions: function () {
return this.get('options');
},
/**
* Returns the type of this metadata field. Possible values are SELECT_TYPE,
* INTEGER_TYPE, and FLOAT_TYPE, GENERIC_TYPE.
*/
getType: function() {
return this.get('type');
},
/**
* Reverts the value to the default_value specified at construction, and updates the
* explicitly_set property.
*/
clear: function() {
this.set({
explicitly_set: false,
value: this.get('default_value')
});
}
});
CMS.Models.MetadataCollection = Backbone.Collection.extend({
model : CMS.Models.Metadata,
comparator: "display_name"
});
CMS.Models.Metadata.SELECT_TYPE = "Select";
CMS.Models.Metadata.INTEGER_TYPE = "Integer";
CMS.Models.Metadata.FLOAT_TYPE = "Float";
CMS.Models.Metadata.GENERIC_TYPE = "Generic";

View File

@@ -0,0 +1,312 @@
if (!CMS.Views['Metadata']) CMS.Views.Metadata = {};
CMS.Views.Metadata.Editor = Backbone.View.extend({
// Model is CMS.Models.MetadataCollection,
initialize : function() {
var tpl = $("#metadata-editor-tpl").text();
if(!tpl) {
console.error("Couldn't load metadata editor template");
}
this.template = _.template(tpl);
this.$el.html(this.template({numEntries: this.collection.length}));
var counter = 0;
var self = this;
this.collection.each(
function (model) {
var data = {
el: self.$el.find('.metadata_entry')[counter++],
model: model
};
if (model.getType() === CMS.Models.Metadata.SELECT_TYPE) {
new CMS.Views.Metadata.Option(data);
}
else if (model.getType() === CMS.Models.Metadata.INTEGER_TYPE ||
model.getType() === CMS.Models.Metadata.FLOAT_TYPE) {
new CMS.Views.Metadata.Number(data);
}
else {
// Everything else is treated as GENERIC_TYPE, which uses String editor.
new CMS.Views.Metadata.String(data);
}
});
},
/**
* Returns the just the modified metadata values, in the format used to persist to the server.
*/
getModifiedMetadataValues: function () {
var modified_values = {};
this.collection.each(
function (model) {
if (model.isModified()) {
modified_values[model.getFieldName()] = model.getValue();
}
}
);
return modified_values;
},
/**
* Returns a display name for the component related to this metadata. This method looks to see
* if there is a metadata entry called 'display_name', and if so, it returns its value. If there
* is no such entry, or if display_name does not have a value set, it returns an empty string.
*/
getDisplayName: function () {
var displayName = '';
this.collection.each(
function (model) {
if (model.get('field_name') === 'display_name') {
var displayNameValue = model.get('value');
// It is possible that there is no display name value set. In that case, return empty string.
displayName = displayNameValue ? displayNameValue : '';
}
}
);
return displayName;
}
});
CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
// Model is CMS.Models.Metadata.
initialize : function() {
var self = this;
var templateName = _.result(this, 'templateName');
// Backbone model cid is only unique within the collection.
this.uniqueId = _.uniqueId(templateName + "_");
var tpl = document.getElementById(templateName).text;
if(!tpl) {
console.error("Couldn't load template: " + templateName);
}
this.template = _.template(tpl);
this.$el.html(this.template({model: this.model, uniqueId: this.uniqueId}));
this.listenTo(this.model, 'change', this.render);
this.render();
},
/**
* The ID/name of the template. Subclasses must override this.
*/
templateName: '',
/**
* Returns the value currently displayed in the editor/view. Subclasses should implement this method.
*/
getValueFromEditor : function () {},
/**
* Sets the value currently displayed in the editor/view. Subclasses should implement this method.
*/
setValueInEditor : function (value) {},
/**
* Sets the value in the model, using the value currently displayed in the view.
*/
updateModel: function () {
this.model.setValue(this.getValueFromEditor());
},
/**
* Clears the value currently set in the model (reverting to the default).
*/
clear: function () {
this.model.clear();
},
/**
* Shows the clear button, if it is not already showing.
*/
showClearButton: function() {
if (!this.$el.hasClass('is-set')) {
this.$el.addClass('is-set');
this.getClearButton().removeClass('inactive');
this.getClearButton().addClass('active');
}
},
/**
* Returns the clear button.
*/
getClearButton: function () {
return this.$el.find('.setting-clear');
},
/**
* Renders the editor, updating the value displayed in the view, as well as the state of
* the clear button.
*/
render: function () {
if (!this.template) return;
this.setValueInEditor(this.model.getDisplayValue());
if (this.model.isExplicitlySet()) {
this.showClearButton();
}
else {
this.$el.removeClass('is-set');
this.getClearButton().addClass('inactive');
this.getClearButton().removeClass('active');
}
return this;
}
});
CMS.Views.Metadata.String = CMS.Views.Metadata.AbstractEditor.extend({
events : {
"change input" : "updateModel",
"keypress .setting-input" : "showClearButton" ,
"click .setting-clear" : "clear"
},
templateName: "metadata-string-entry",
getValueFromEditor : function () {
return this.$el.find('#' + this.uniqueId).val();
},
setValueInEditor : function (value) {
this.$el.find('input').val(value);
}
});
CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({
events : {
"change input" : "updateModel",
"keypress .setting-input" : "keyPressed",
"change .setting-input" : "changed",
"click .setting-clear" : "clear"
},
render: function () {
CMS.Views.Metadata.AbstractEditor.prototype.render.apply(this);
if (!this.initialized) {
var numToString = function (val) {
return val.toFixed(4);
};
var min = "min";
var max = "max";
var step = "step";
var options = this.model.getOptions();
if (options.hasOwnProperty(min)) {
this.min = Number(options[min]);
this.$el.find('input').attr(min, numToString(this.min));
}
if (options.hasOwnProperty(max)) {
this.max = Number(options[max]);
this.$el.find('input').attr(max, numToString(this.max));
}
var stepValue = undefined;
if (options.hasOwnProperty(step)) {
// Parse step and convert to String. Polyfill doesn't like float values like ".1" (expects "0.1").
stepValue = numToString(Number(options[step]));
}
else if (this.isIntegerField()) {
stepValue = "1";
}
if (stepValue !== undefined) {
this.$el.find('input').attr(step, stepValue);
}
// Manually runs polyfill for input number types to correct for Firefox non-support.
// inputNumber will be undefined when unit test is running.
if ($.fn.inputNumber) {
this.$el.find('.setting-input-number').inputNumber();
}
this.initialized = true;
}
return this;
},
templateName: "metadata-number-entry",
getValueFromEditor : function () {
return this.$el.find('#' + this.uniqueId).val();
},
setValueInEditor : function (value) {
this.$el.find('input').val(value);
},
/**
* Returns true if this view is restricted to integers, as opposed to floating points values.
*/
isIntegerField : function () {
return this.model.getType() === 'Integer';
},
keyPressed: function (e) {
this.showClearButton();
// This first filtering if statement is take from polyfill to prevent
// non-numeric input (for browsers that don't use polyfill because they DO have a number input type).
var _ref, _ref1;
if (((_ref = e.keyCode) !== 8 && _ref !== 9 && _ref !== 35 && _ref !== 36 && _ref !== 37 && _ref !== 39) &&
((_ref1 = e.which) !== 45 && _ref1 !== 46 && _ref1 !== 48 && _ref1 !== 49 && _ref1 !== 50 && _ref1 !== 51
&& _ref1 !== 52 && _ref1 !== 53 && _ref1 !== 54 && _ref1 !== 55 && _ref1 !== 56 && _ref1 !== 57)) {
e.preventDefault();
}
// For integers, prevent decimal points.
if (this.isIntegerField() && e.keyCode === 46) {
e.preventDefault();
}
},
changed: function () {
// Limit value to the range specified by min and max (necessary for browsers that aren't using polyfill).
var value = this.getValueFromEditor();
if ((this.max !== undefined) && value > this.max) {
value = this.max;
} else if ((this.min != undefined) && value < this.min) {
value = this.min;
}
this.setValueInEditor(value);
this.updateModel();
}
});
CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({
events : {
"change select" : "updateModel",
"click .setting-clear" : "clear"
},
templateName: "metadata-option-entry",
getValueFromEditor : function () {
var selectedText = this.$el.find('#' + this.uniqueId).find(":selected").text();
var selectedValue;
_.each(this.model.getOptions(), function (modelValue) {
if (modelValue === selectedText) {
selectedValue = modelValue;
}
else if (modelValue['display_name'] === selectedText) {
selectedValue = modelValue['value'];
}
});
return selectedValue;
},
setValueInEditor : function (value) {
// Value here is the json value as used by the field. The choice may instead be showing display names.
// Find the display name matching the value passed in.
_.each(this.model.getOptions(), function (modelValue) {
if (modelValue['value'] === value) {
value = modelValue['display_name'];
}
});
this.$el.find('#' + this.uniqueId + " option").filter(function() {
return $(this).text() === value;
}).prop('selected', true);
}
});

View File

@@ -814,7 +814,7 @@ hr.divide {
line-height: 26px;
color: $white;
pointer-events: none;
opacity: 0;
opacity: 0.0;
&:after {
content: '';

View File

@@ -149,11 +149,11 @@ abbr[title] {
margin-left: 20px;
}
li {
opacity: .8;
opacity: 0.8;
&:ui-state-active {
background-color: rgba(255, 255, 255, .3);
opacity: 1;
opacity: 1.0;
font-weight: 400;
}
a:focus {

View File

@@ -95,12 +95,12 @@
// bounce in
@mixin bounceIn {
0% {
opacity: 0;
opacity: 0.0;
@include transform(scale(0.3));
}
50% {
opacity: 1;
opacity: 1.0;
@include transform(scale(1.05));
}
@@ -128,12 +128,12 @@
// bounce in
@mixin bounceOut {
0% {
opacity: 0;
opacity: 0.0;
@include transform(scale(0.3));
}
50% {
opacity: 1;
opacity: 1.0;
@include transform(scale(1.05));
}
@@ -146,12 +146,12 @@
}
50% {
opacity: 1;
opacity: 1.0;
@include transform(scale(1.05));
}
100% {
opacity: 0;
opacity: 0.0;
@include transform(scale(0.3));
}
}

View File

@@ -124,7 +124,6 @@ code {
.CodeMirror {
font-size: 13px;
border: 1px solid $darkGrey;
background: #fff;
}

View File

@@ -243,7 +243,7 @@
left: -7px;
top: 47px;
width: 140px;
opacity: 0;
opacity: 0.0;
pointer-events: none;
}
@@ -558,7 +558,7 @@ body.signin .nav-not-signedin-signup {
.wrapper-nav-sub {
@include transition (opacity 1.0s ease-in-out 0s);
opacity: 0;
opacity: 0.0;
pointer-events: none;
&.is-shown {

View File

@@ -627,7 +627,7 @@
pointer-events: none;
.prompt {
opacity: 0;
opacity: 0.0;
}
}

View File

@@ -254,7 +254,7 @@ body.course.checklists {
.task-support {
@extend .t-copy-sub2;
@include transition(opacity .15s .25s ease-in-out);
opacity: 0;
opacity: 0.0;
pointer-events: none;
}
}
@@ -267,7 +267,7 @@ body.course.checklists {
float: right;
width: flex-grid(2,9);
margin: ($baseline/2) 0 0 flex-gutter();
opacity: 0;
opacity: 0.0;
pointer-events: none;
text-align: right;

View File

@@ -59,7 +59,7 @@ body.dashboard {
top: 15px;
right: $baseline;
padding: ($baseline/4) ($baseline/2);
opacity: 0;
opacity: 0.0;
pointer-events: none;
&:hover {

View File

@@ -162,7 +162,7 @@ body.index {
position: absolute;
bottom: -30px;
right: ($baseline/2);
opacity: 0;
opacity: 0.0;
[class^="icon-"] {
@include font-size(18);

View File

@@ -21,7 +21,7 @@ body.course.settings {
font-size: 14px;
}
.message-status {
.message-status {
display: none;
@include border-top-radius(2px);
@include box-sizing(border-box);
@@ -386,6 +386,11 @@ body.course.settings {
#course-overview {
height: ($baseline*20);
}
//adds back in CodeMirror border removed due to Unit page styling of component editors
.CodeMirror {
border: 1px solid $gray-l2;
}
}
// specific fields - video
@@ -698,7 +703,7 @@ body.course.settings {
.tip {
@include transition (opacity 0.5s ease-in-out 0s);
opacity: 0;
opacity: 0.0;
position: absolute;
bottom: ($baseline*1.25);
}
@@ -713,7 +718,7 @@ body.course.settings {
input.error {
& + .tip {
opacity: 0;
opacity: 0.0;
}
}
}

View File

@@ -41,38 +41,23 @@ body.course.static-pages {
@include edit-box;
@include box-shadow(none);
display: none;
padding: 20px;
padding: 0;
border-radius: 2px 2px 0 0;
.metadata_edit {
margin-bottom: 20px;
font-size: 13px;
//Overrides general edit-box mixin
.row {
margin-bottom: 0px;
}
li {
margin-bottom: 10px;
// This duplicates the styling from Unit page editing
.module-actions {
@include box-shadow(inset 0 1px 1px $shadow);
padding: 0px 0 10px 10px;
background-color: $gray-l6;
.save-button {
margin: ($baseline/2) 8px 0 0;
}
label {
display: inline-block;
margin-right: 10px;
}
}
h3 {
margin-bottom: 10px;
font-size: 18px;
font-weight: 700;
}
h5 {
margin-bottom: 8px;
color: #fff;
font-weight: 700;
}
.save-button {
margin-top: 10px;
margin: 15px 8px 0 0;
}
}
}
@@ -215,3 +200,4 @@ body.course.static-pages {
outline: 0;
}
}

View File

@@ -24,7 +24,7 @@ body.course.unit {
border: none;
.rendered-component {
padding: 0 20px;
padding: 0 $baseline;
}
}
}
@@ -32,7 +32,7 @@ body.course.unit {
.unit-body {
.unit-name-input {
padding: 20px 40px;
padding: $baseline 2*$baseline;
label {
display: block;
@@ -73,15 +73,15 @@ body.course.unit {
letter-spacing: 1px;
text-transform: uppercase;
}
// ====================
// Component List Meta
.components {
> li {
position: relative;
z-index: 10;
margin: 20px 40px;
margin: $baseline 2*$baseline;
.title {
margin: 0 0 15px 0;
@@ -91,23 +91,26 @@ body.course.unit {
}
}
// ====================
// New Components
&.new-component-item {
margin: 20px 0px;
margin: $baseline 0px;
border-top: 1px solid $mediumGrey;
box-shadow: 0 2px 1px rgba(182, 182, 182, 0.75) inset;
background-color: $lightGrey;
margin-bottom: 0px;
padding-bottom: 20px;
padding-bottom: $baseline;
.new-component-button {
display: block;
padding: 20px;
padding: $baseline;
text-align: center;
color: #edf1f5;
}
h5 {
margin: 20px 0px;
margin: $baseline 0px;
color: #fff;
font-weight: 600;
font-size: 18px;
@@ -132,7 +135,7 @@ body.course.unit {
height: 100px;
color: #fff;
margin-right: 15px;
margin-bottom: 20px;
margin-bottom: $baseline;
border-radius: 8px;
font-size: 15px;
line-height: 14px;
@@ -144,7 +147,7 @@ body.course.unit {
bottom: 5px;
left: 0;
width: 100%;
padding: 10px;
padding: $baseline/2;
@include box-sizing(border-box);
color: #fff;
}
@@ -153,7 +156,7 @@ body.course.unit {
.new-component-templates {
display: none;
margin: 20px 40px 20px 40px;
margin: $baseline 2*$baseline;
border-radius: 3px;
border: 1px solid $mediumGrey;
background-color: #fff;
@@ -161,7 +164,7 @@ body.course.unit {
@include clearfix;
.cancel-button {
margin: 20px 0px 10px 10px;
margin: $baseline 0px $baseline/2 $baseline/2;
@include white-button;
}
@@ -171,9 +174,9 @@ body.course.unit {
// specific menu types
&.new-component-problem {
padding-bottom:10px;
padding-bottom: $baseline/2;
.ss-icon, .editor-indicator {
[class^="icon-"], .editor-indicator {
display: inline-block;
}
@@ -208,7 +211,7 @@ body.course.unit {
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
li:first-child {
margin-left: 20px;
margin-left: $baseline;
}
li {
@@ -219,21 +222,21 @@ body.course.unit {
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
background-color: tint($lightBluishGrey, 10%);
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
opacity:.8;
opacity: 0.8;
&:hover {
opacity:1;
opacity: 0.9;
background-color: tint($lightBluishGrey, 20%);
}
&.ui-state-active {
border: 0px;
@include active;
opacity:1;
opacity: 1.0;
}
}
a{
a {
display: block;
padding: 15px 25px;
font-size: 15px;
@@ -280,14 +283,14 @@ body.course.unit {
a {
@include clearfix();
display: block;
padding: 7px 20px;
padding: 7px $baseline;
border-bottom: none;
font-weight: 500;
.name {
float: left;
.ss-icon {
[class^="icon-"] {
@include transition(opacity .15s);
display: inline-block;
top: 1px;
@@ -308,14 +311,14 @@ body.course.unit {
opacity: 0.3;
}
.ss-icon, .editor-indicator {
[class^="icon-"], .editor-indicator {
display: none;
}
&:hover {
color: #fff;
.ss-icon {
[class^="icon-"] {
opacity: 1.0;
}
@@ -355,6 +358,9 @@ body.course.unit {
}
}
// ====================
// Component Drag and Drop, Non-Edit Module Rendering, Styling
.component {
border: 1px solid $lightBluishGrey2;
border-radius: 3px;
@@ -401,7 +407,7 @@ body.course.unit {
}
.xmodule_display {
padding: 40px 20px 20px;
padding: 2*$baseline $baseline $baseline;
overflow-x: auto;
h1 {
@@ -409,36 +415,24 @@ body.course.unit {
margin-left: 0;
}
}
// ====================
// Component Editing
.wrapper-component-editor {
z-index: 9999;
position: relative;
background: $lightBluishGrey2;
background: $white;
}
.component-editor {
@include edit-box;
@include box-shadow(none);
display: none;
padding: 20px;
padding: 0;
border-radius: 2px 2px 0 0;
.metadata_edit {
margin-bottom: 20px;
font-size: 13px;
li {
margin-bottom: 10px;
}
label {
display: inline-block;
margin-right: 10px;
}
}
h3 {
margin-bottom: 10px;
margin-bottom: $baseline/2;
font-size: 18px;
font-weight: 700;
}
@@ -449,214 +443,286 @@ body.course.unit {
font-weight: 700;
}
.save-button {
margin-top: 10px;
margin: 15px 8px 0 0;
}
}
}
.unit-settings {
.window-contents {
padding: 10px 20px;
}
.unit-actions {
border-bottom: none;
padding-bottom: 0;
}
.published-alert {
display: none;
padding: 10px;
border: 1px solid #edbd3c;
border-radius: 3px;
background: #fbf6e1;
font-size: 14px;
line-height: 1.4;
div {
margin-top: 15px;
}
}
input[type="radio"] {
margin-right: 7px;
}
.status {
font-size: 12px;
strong {
font-weight: 700;
}
}
.preview-button, .view-button {
@include white-button;
margin-bottom: 10px;
}
.publish-button {
@include orange-button;
}
.delete-button {
@include blue-button;
}
.delete-draft {
display: inline-block;
}
.delete-button,
.preview-button,
.publish-button,
.view-button {
font-size: 11px;
margin-top: 10px;
padding: 6px 15px 8px;
}
}
.unit-history {
&.collapsed {
h4 {
border-bottom: none;
border-radius: 3px;
.row {
margin-bottom: 0px;
}
.window-contents {
display: none;
}
}
// Module Actions, also used for Static Pages
.module-actions {
@include box-shadow(inset 0 1px 1px $shadow);
padding: 0 0 $baseline $baseline;
background-color: $gray-l6;
ol {
border: 1px solid #ced2db;
li {
display: block;
padding: 6px 8px 8px 10px;
background: #edf1f5;
font-size: 12px;
&:hover {
background: #fffcf1;
.item-actions {
display: block;
}
}
&.checked {
background: #d1dae3;
}
.item-actions {
display: none;
}
input[type="radio"] {
margin-right: 7px;
.save-button {
margin: ($baseline/2) 8px 0 0;
}
}
}
}
.unit-location {
.url {
width: 100%;
margin-bottom: 10px;
@include box-shadow(none);
}
.draft-tag,
.hidden-tag,
.private-tag,
.has-new-draft-tag {
font-size: 8px;
}
.window-contents > ol {
@include tree-view;
.section-item {
display: inline-block;
width: 100%;
font-size: 11px;
padding: 2px 8px 4px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@include box-sizing(border-box);
}
ol {
.section-item {
padding-left: 20px;
}
.new-unit-item {
margin-left: 20px;
}
}
ol ol {
.section-item {
padding-left: 34px;
}
.new-unit-item {
margin: 0 0 10px 41px;
}
}
}
}
.edit-state-draft {
.visibility,
.edit-draft-message,
.view-button {
display: none;
}
.published-alert {
display: block;
}
}
.edit-state-public {
.delete-draft,
.component-actions,
.new-component-item,
.editing-draft-alert,
.publish-draft-message,
.preview-button {
display: none;
}
.published-alert {
display: block;
}
.drag-handle {
display: none !important;
}
}
.edit-state-private {
.delete-draft,
.publish-draft,
.editing-draft-alert,
.create-draft,
.view-button {
display: none;
}
}
}
// editing units from courseware
// Edit Header (Component Name, Mode-Editor, Mode-Settings)
.component-edit-header {
@include box-sizing(border-box);
padding: 18px 0 18px $baseline;
top: 0;
right: 0;
background-color: $blue;
border-bottom: 1px solid $blue-d2;
color: $white;
//Component Name
.component-name {
@extend .t-copy-sub1;
width: 50%;
color: $white;
font-weight: 600;
em {
display: inline-block;
margin-right: ($baseline/4);
font-weight: 400;
color: $white;
}
}
//Nav-Edit Modes
.nav-edit-modes {
list-style: none;
right: 0;
top: 0;
position: absolute;
padding: 12px ($baseline*0.75);
.mode {
display: inline-block;
margin-left: 8px;
&.inactive-mode{
display: none;
}
&.active-mode a {
@include blue-button;
&.is-set {
@include transition(box-shadow 0.5 ease-in-out);
@include linear-gradient($blue, $blue);
color: $blue-d1;
box-shadow: inset 0 1px 2px 1px $shadow-l1;
background-color: $blue-d4;
cursor: default;
&:hover {
box-shadow: inset 0 1px 2px 1px $shadow;
}
}
}
}
}
}
// Editor Wrapper
.wrapper-comp-editor {
display: block;
// Because the editor may be a CodeMirror editor (which must be visible at the time it is created
// in order for it to properly initialize), we must toggle "is-inactive" instead of the more common "is-active".
&.is-inactive {
display: none;
}
}
// Settings Wrapper
.wrapper-comp-settings {
display: none;
&.is-active {
display: block;
}
//settings-list
.list-input.settings-list {
margin: 0;
padding: 0;
list-style: none;
overflow: auto;
max-height: 400px;
//chrome scrollbar visibility correction
&::-webkit-scrollbar {
-webkit-appearance: none;
width: 11px;
height: 11px;
}
&::-webkit-scrollbar-thumb {
border-radius: 8px;
border: 2px solid $gray-l2;
background-color: rgba(0, 0, 0, .5);
}
//component-setting-entry
.field.comp-setting-entry {
background-color: $white;
padding: $baseline;
border-bottom: 1px solid $gray-l2;
opacity: 0.7;
&:last-child {
//margin-bottom: 0;
}
//no required component settings currently
&.required {
label {
//font-weight: 600;
}
label:after {
//margin-left: ($baseline/4);
//content: "*";
}
}
&:hover {
@include transition(opacity 0.25s ease-in-out);
opacity: 1.0;
}
&.is-set {
opacity: 1.0;
background-color: $white;
.setting-input {
color: $blue-l1;
}
}
.wrapper-comp-setting{
display: inline-block;
min-width: 300px;
width: 45%;
top: 0;
vertical-align: top;
margin-bottom:5px;
position: relative;
}
label.setting-label {
@extend .t-copy-sub1;
@include transition(color, 0.15s, ease-in-out);
font-weight: 400;
vertical-align: middle;
display: inline-block;
position: relative;
left: 0;
min-width: 100px;
width: 35%;
&.is-focused {
color: $blue;
}
}
input, select, input[type="number"] {
@include placeholder($gray-l4);
@include font-size(16);
@include size(100%,100%);
padding: ($baseline/2);
min-width: 100px;
width: 45%;
//&.long {
//
//}
//&.short {
//}
//&.error {
// border-color: $red;
//}
//&:focus {
// + .tip {
// color: $gray;
// }
border-radius: 3px;
border: 1px solid $gray-l2;
text-overflow: ellipsis;
}
input[type="number"] {
width: 38.5%;
@include box-shadow(0 1px 2px $shadow-l1 inset);
//For webkit browsers which render number fields differently, make input wider.
-moz-column-width: {
width: 32%;
}
&:active {
background-color: #FFFCF1;
}
}
select {
//@include box-shadow(0 1px 2px $shadow-l1 inset);
&:focus {
@include box-shadow(0 0 1px $shadow);
@include transition(opacity 0.25s ease-in-out);
background-color: $yellow;
}
&:active {
background-color: $yellow;
}
}
.action.setting-clear {
@include font-size(11);
color: $gray;
width: 25px;
height: 25px;
vertical-align: middle;
padding: 5px;
border-radius: 50%;
margin: 0 $baseline/2;
box-shadow: none;
text-shadow: none;
border: 1px solid $gray-l1;
background-color: $gray-l4;
&:hover {
@include box-shadow(0 1px 1px $shadow);
@include transition(opacity 0.15s ease-in-out);
background-color: $blue-s3;
border: 1px solid $blue-s3;
color: $white;
}
&.inactive {
visibility: hidden;
}
}
.tip.setting-help {
@include font-size(12);
display: inline-block;
font-color: $gray-l6;
min-width: 260px;
width: 50%;
vertical-align: top;
}
}
}
}
// ====================
// Editing Units from Courseware
body.unit {
.component {
@@ -678,3 +744,224 @@ body.unit {
}
}
}
// ====================
// Unit Page Sidebar
.unit-settings {
.window-contents {
padding: $baseline/2 $baseline;
}
.unit-actions {
border-bottom: none;
padding-bottom: 0;
}
.published-alert {
display: none;
padding: $baseline/2;
border: 1px solid #edbd3c;
border-radius: 3px;
background: #fbf6e1;
font-size: 14px;
line-height: 1.4;
div {
margin-top: 15px;
}
}
input[type="radio"] {
margin-right: 7px;
}
.status {
font-size: 12px;
strong {
font-weight: 700;
}
}
.preview-button, .view-button {
@include white-button;
margin-bottom: $baseline/2;
}
.publish-button {
@include orange-button;
}
.delete-button {
@include blue-button;
}
.delete-draft {
display: inline-block;
}
.delete-button,
.preview-button,
.publish-button,
.view-button {
font-size: 11px;
margin-top: $baseline/2;
padding: 6px 15px 8px;
}
}
.unit-history {
&.collapsed {
h4 {
border-bottom: none;
border-radius: 3px;
}
.window-contents {
display: none;
}
}
ol {
border: 1px solid #ced2db;
li {
display: block;
padding: 6px 8px 8px $baseline/2;
background: #edf1f5;
font-size: 12px;
&:hover {
background: #fffcf1;
.item-actions {
display: block;
}
}
&.checked {
background: #d1dae3;
}
.item-actions {
display: none;
}
input[type="radio"] {
margin-right: 7px;
}
}
}
}
.unit-location {
.url {
@include box-shadow(none);
width: 100%;
margin-bottom: $baseline/2;
}
.draft-tag,
.hidden-tag,
.private-tag,
.has-new-draft-tag {
font-size: 8px;
}
.window-contents > ol {
@include tree-view;
.section-item {
@include box-sizing(border-box);
display: inline-block;
width: 100%;
font-size: 11px;
padding: 2px 8px 4px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
ol {
.section-item {
padding-left: $baseline;
}
.new-unit-item {
margin-left: $baseline;
}
}
ol ol {
.section-item {
padding-left: 34px;
}
.new-unit-item {
margin: 0 0 $baseline 41px;
}
}
}
}
.edit-state-draft {
.visibility,
.edit-draft-message,
.view-button {
display: none;
}
.published-alert {
display: block;
}
}
.edit-state-public {
.delete-draft,
.component-actions,
.new-component-item,
.editing-draft-alert,
.publish-draft-message,
.preview-button {
display: none;
}
.published-alert {
display: block;
}
.drag-handle {
display: none !important;
}
}
.edit-state-private {
.delete-draft,
.publish-draft,
.editing-draft-alert,
.create-draft,
.view-button {
display: none;
}
}
// ====================
// Latex Compiler
.launch-latex-compiler {
background-color: $white;
padding: $baseline/2 0 $baseline/2 $baseline;
border-bottom: 1px solid $gray-l2;
opacity: 0.8;
&:hover {
@include transition(opacity 0.25s ease-in-out);
opacity: 1.0s;
}
}
// hides latex compiler button if settings mode is-active
div.wrapper-comp-editor.is-inactive ~ div.launch-latex-compiler{
display: none;
}

View File

@@ -212,6 +212,7 @@ body.course.updates {
@include edit-box;
position: absolute;
right: 0;
top: 0;
z-index: 10001;
width: 800px;
padding: 30px;

View File

@@ -61,6 +61,8 @@
<div class="wrapper wrapper-view">
<%include file="widgets/header.html" />
## remove this block after advanced settings notification is rewritten
<%block name="view_alerts"></%block>
<div id="page-alert"></div>
<%block name="content"></%block>
@@ -72,9 +74,13 @@
<%include file="widgets/footer.html" />
<%include file="widgets/tender.html" />
## remove this block after advanced settings notification is rewritten
<%block name="view_notifications"></%block>
<div id="page-notification"></div>
</div>
## remove this block after advanced settings notification is rewritten
<%block name="view_prompts"></%block>
<div id="page-prompt"></div>
<%block name="jsextra"></%block>
</body>

View File

@@ -1,19 +1,41 @@
<%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='static_content.html'/>
<script type="text/javascript" src="${static.url('js/models/metadata_model.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/metadata_editor_view.js')}"></script>
<script src="${static.url('js/vendor/html5-input-polyfills/number-polyfill.js')}"></script>
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/html5-input-polyfills/number-polyfill.css')}" />
<div class="wrapper wrapper-component-editor">
<div class="component-editor">
<div class="module-editor">
${editor}
</div>
<div class="row module-actions">
<a href="#" class="save-button">Save</a>
<a href="#" class="cancel-button">Cancel</a>
</div>
</div>
<div class="component-edit-header">
<span class="component-name"></span>
<ul class="nav-edit-modes">
<li id="editor-mode" class="mode active-mode" aria-controls="editor-tab" role="tab">
<a href="#">${_("Editor")}</a>
</li>
<li id="settings-mode" class="mode active-mode" aria-controls="settings-tab" role="tab">
<a href="#">${_("Settings")}</a>
</li>
</ul>
</div> <!-- Editor Header -->
<div class="component-edit-modes">
<div class="module-editor">
${editor}
</div>
</div>
<div class="row module-actions">
<a href="#" class="save-button">${_("Save")}</a>
<a href="#" class="cancel-button">${_("Cancel")}</a>
</div> <!-- Module Actions-->
</div>
</div>
<div class="component-actions">
<a href="#" class="edit-button standard"><span class="edit-icon"></span>Edit</a>
<a href="#" class="delete-button standard"><span class="delete-icon"></span>Delete</a>
<a href="#" class="edit-button standard"><span class="edit-icon"></span>${_("Edit")}</a>
<a href="#" class="delete-button standard"><span class="delete-icon"></span>${_("Delete")}</a>
</div>
<a data-tooltip="Drag to reorder" href="#" class="drag-handle"></a>
<a data-tooltip='${_("Drag to reorder")}' href="#" class="drag-handle"></a>
${preview}

View File

@@ -0,0 +1,6 @@
<ul class="list-input settings-list">
<% _.each(_.range(numEntries), function() { %>
<li class="field comp-setting-entry metadata_entry" id="settings-listing">
</li>
<% }) %>
</ul>

View File

@@ -0,0 +1,8 @@
<div class="wrapper-comp-setting">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
<input class="input setting-input setting-input-number" type="number" id="<%= uniqueId %>" value='<%= model.get("value") %>'/>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>

View File

@@ -0,0 +1,16 @@
<div class="wrapper-comp-setting">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
<select class="input setting-input" id="<%= uniqueId %>" name="<%= model.get('display_name') %>">
<% _.each(model.get('options'), function(option) { %>
<% if (option.display_name !== undefined) { %>
<option value="<%= option['display_name'] %>"><%= option['display_name'] %></option>
<% } else { %>
<option value="<%= option %>"><%= option %></option>
<% } %>
<% }) %>
</select>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>

View File

@@ -0,0 +1,8 @@
<div class="wrapper-comp-setting">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name') %></label>
<input class="input setting-input" type="text" id="<%= uniqueId %>" value='<%= model.get("value") %>'/>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>

View File

@@ -3,6 +3,12 @@
<%namespace name='static' file='../static_content.html'/>
%if not user_logged_in:
<%block name="bodyclass">
not-signedin
</%block>
%endif
<%block name="content">
<section class="container activation">
@@ -18,7 +24,7 @@
%if user_logged_in:
Visit your <a href="/">dashboard</a> to see your courses.
%else:
You can now <a href="#login-modal" rel="leanModal">login</a>.
You can now <a href="${reverse('login')}">login</a>.
%endif
</p>
</section>

View File

@@ -78,22 +78,13 @@
% endif
<div class="tab current" id="tab1">
<ul class="new-component-template">
% for name, location, has_markdown, is_empty in templates:
% for name, location, has_markdown in templates:
% if has_markdown or type != "problem":
% if is_empty:
<li class="editor-md empty">
<a href="#" data-location="${location}" id="${location}">
<span class="name"> ${name}</span>
</a>
</li>
% else:
<li class="editor-md">
<a href="#" data-location="${location}" id="${location}">
<span class="name"> ${name}</span>
</a>
</li>
% endif
<li class="editor-md">
<a href="#" id="${location}" data-location="${location}">
<span class="name"> ${name}</span>
</a>
</li>
% endif
%endfor
@@ -102,23 +93,13 @@
% if type == "problem":
<div class="tab" id="tab2">
<ul class="new-component-template">
% for name, location, has_markdown, is_empty in templates:
% for name, location, has_markdown in templates:
% if not has_markdown:
% if is_empty:
<li class="editor-manual empty">
<a href="#" data-location="${location}" id="${location}">
<span class="name">${name}</span>
</a>
</li>
% else:
<li class="editor-manual">
<a href="#" data-location="${location}" id="${location}">
<span class="name"> ${name}</span>
</a>
</li>
% endif
<li class="editor-manual">
<a href="#" id="${location}" data-location="${location}">
<span class="name"> ${name}</span>
</a>
</li>
% endif
% endfor
</ul>

View File

@@ -1,12 +1,16 @@
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper-comp-editor" id="editor-tab">
<section class="html-editor editor">
<ul class="editor-tabs">
<li><a href="#" class="visual-tab tab current" data-tab="visual">${_("Visual")}</a></li>
<li><a href="#" class="html-tab tab" data-tab="advanced">${_("HTML")}</a></li>
</ul>
<div class="row">
<textarea class="tiny-mce">${data | h}</textarea>
<textarea name="" class="edit-box">${data | h}</textarea>
</div>
</section>
</div>
<%include file="metadata-edit.html" />
<section class="html-editor editor">
<ul class="editor-tabs">
<li><a href="#" class="visual-tab tab current" data-tab="visual">Visual</a></li>
<li><a href="#" class="html-tab tab" data-tab="advanced">HTML</a></li>
</ul>
<div class="row">
<textarea class="tiny-mce">${data | h}</textarea>
<textarea name="" class="edit-box">${data | h}</textarea>
</div>
</section>

View File

@@ -1,49 +1,43 @@
<%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='../static_content.html'/>
<%
import hashlib
from xmodule.fields import StringyInteger, StringyFloat
import copy
import json
hlskey = hashlib.md5(module.location.url()).hexdigest()
%>
<section class="metadata_edit">
<ul>
% for field_name, field_value in editable_metadata_fields.items():
<li>
% if field_name == 'source_code':
% if field_value['explicitly_set'] is True:
<a href="#hls-modal-${hlskey}" style="color:yellow;" id="hls-trig-${hlskey}" >Edit High Level Source</a>
% endif
% else:
<label>${field_value['field'].display_name}:</label>
<input type='text' data-metadata-name='${field_value["field"].display_name}'
## This is a hack to keep current behavior for weight and attempts (empty will parse OK as unset).
## This hack will go away with our custom editors.
% if field_value["value"] == None and (isinstance(field_value["field"], StringyFloat) or isinstance(field_value["field"], StringyInteger)):
value = ''
% else:
value='${field_value["field"].to_json(field_value["value"])}'
% endif
size='60' />
## Change to True to see all the information being passed through.
% if False:
<label>Help: ${field_value['field'].help}</label>
<label>Type: ${type(field_value['field']).__name__}</label>
<label>Inheritable: ${field_value['inheritable']}</label>
<label>Showing inherited value: ${field_value['inheritable'] and not field_value['explicitly_set']}</label>
<label>Explicitly set: ${field_value['explicitly_set']}</label>
<label>Default value: ${field_value['default_value']}</label>
% if field_value['field'].values:
<label>Possible values:</label>
% for value in field_value['field'].values:
<label>${value}</label>
% endfor
% endif
% endif
% endif
</li>
% endfor
</ul>
% if 'source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set']:
## js templates
<script id="metadata-editor-tpl" type="text/template">
<%static:include path="js/metadata-editor.underscore" />
</script>
<script id="metadata-number-entry" type="text/template">
<%static:include path="js/metadata-number-entry.underscore" />
</script>
<script id="metadata-string-entry" type="text/template">
<%static:include path="js/metadata-string-entry.underscore" />
</script>
<script id="metadata-option-entry" type="text/template">
<%static:include path="js/metadata-option-entry.underscore" />
</script>
<% showHighLevelSource='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] %>
<% metadata_field_copy = copy.copy(editable_metadata_fields) %>
## Delete 'source_code' field (if it exists) so metadata editor view does not attempt to render it.
% if 'source_code' in editable_metadata_fields:
## source-edit.html needs access to the 'source_code' value, so delete from a copy.
<% del metadata_field_copy['source_code'] %>
% endif
% if showHighLevelSource:
<div class="launch-latex-compiler">
<a href="#hls-modal-${hlskey}" id="hls-trig-${hlskey}">${_("Launch Latex Source Compiler")}</a>
</div>
<%include file="source-edit.html" />
% endif
% endif
</section>
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy) | h}'/>

View File

@@ -1,4 +1,4 @@
<%include file="metadata-edit.html" />
<div class="wrapper-comp-editor" id="editor-tab">
<section class="combinedopenended-editor editor">
<div class="row">
%if enable_markdown:
@@ -93,3 +93,5 @@
</div>
</article>
</script>
</div>
<%include file="metadata-edit.html" />

View File

@@ -1,27 +1,29 @@
<%include file="metadata-edit.html" />
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper-comp-editor" id="editor-tab">
<section class="problem-editor editor">
<div class="row">
%if enable_markdown:
<div class="editor-bar">
<ul class="format-buttons">
<li><a href="#" class="header-button" data-tooltip="Heading 1"><span
<li><a href="#" class="header-button" data-tooltip='${_("Heading 1")}'><span
class="problem-editor-icon heading1"></span></a></li>
<li><a href="#" class="multiple-choice-button" data-tooltip="Multiple Choice"><span
<li><a href="#" class="multiple-choice-button" data-tooltip='${_("Multiple Choice")}'><span
class="problem-editor-icon multiple-choice"></span></a></li>
<li><a href="#" class="checks-button" data-tooltip="Checkboxes"><span
<li><a href="#" class="checks-button" data-tooltip='${_("Checkboxes")}'><span
class="problem-editor-icon checks"></span></a></li>
<li><a href="#" class="string-button" data-tooltip="Text Input"><span
<li><a href="#" class="string-button" data-tooltip='${_("Text Input")}'><span
class="problem-editor-icon string"></span></a></li>
<li><a href="#" class="number-button" data-tooltip="Numerical Input"><span
<li><a href="#" class="number-button" data-tooltip='${_("Numerical Input")}'><span
class="problem-editor-icon number"></span></a></li>
<li><a href="#" class="dropdown-button" data-tooltip="Dropdown"><span
<li><a href="#" class="dropdown-button" data-tooltip='${_("Dropdown")}'><span
class="problem-editor-icon dropdown"></span></a></li>
<li><a href="#" class="explanation-button" data-tooltip="Explanation"><span
<li><a href="#" class="explanation-button" data-tooltip='${_("Explanation")}'><span
class="problem-editor-icon explanation"></span></a></li>
</ul>
<ul class="editor-tabs">
<li><a href="#" class="xml-tab advanced-toggle" data-tab="xml">Advanced Editor</a></li>
<li><a href="#" class="cheatsheet-toggle" data-tooltip="Toggle Cheatsheet">?</a></li>
<li><a href="#" class="xml-tab advanced-toggle" data-tab="xml">${_("Advanced Editor")}</a></li>
<li><a href="#" class="cheatsheet-toggle" data-tooltip='${_("Toggle Cheatsheet")}'>?</a></li>
</ul>
</div>
<textarea class="markdown-box">${markdown | h}</textarea>
@@ -34,7 +36,7 @@
<article class="simple-editor-cheatsheet">
<div class="cheatsheet-wrapper">
<div class="row">
<h6>Heading 1</h6>
<h6>${_("Heading 1")}</h6>
<div class="col sample heading-1">
<img src="/static/img/header-example.png" />
</div>
@@ -45,7 +47,7 @@
</div>
</div>
<div class="row">
<h6>Multiple Choice</h6>
<h6>${_("Multiple Choice")}</h6>
<div class="col sample multiple-choice">
<img src="/static/img/choice-example.png" />
</div>
@@ -56,7 +58,7 @@
</div>
</div>
<div class="row">
<h6>Checkboxes</h6>
<h6>${_("Checkboxes")}</h6>
<div class="col sample check-multiple">
<img src="/static/img/multi-example.png" />
</div>
@@ -67,7 +69,7 @@
</div>
</div>
<div class="row">
<h6>Text Input</h6>
<h6>${_("Text Input")}</h6>
<div class="col sample string-response">
<img src="/static/img/string-example.png" />
</div>
@@ -76,7 +78,7 @@
</div>
</div>
<div class="row">
<h6>Numerical Input</h6>
<h6>${_("Numerical Input")}</h6>
<div class="col sample numerical-response">
<img src="/static/img/number-example.png" />
</div>
@@ -85,7 +87,7 @@
</div>
</div>
<div class="row">
<h6>Dropdown</h6>
<h6>${_("Dropdown")}</h6>
<div class="col sample option-reponse">
<img src="/static/img/select-example.png" />
</div>
@@ -94,7 +96,7 @@
</div>
</div>
<div class="row">
<h6>Explanation</h6>
<h6>${_("Explanation")}</h6>
<div class="col sample explanation">
<img src="/static/img/explanation-example.png" />
</div>
@@ -105,3 +107,5 @@
</div>
</article>
</script>
</div>
<%include file="metadata-edit.html" />

View File

@@ -1,4 +1,6 @@
<div class="wrapper-comp-editor" id="editor-tab">
<section class="raw-edit">
<textarea name="" class="edit-box" rows="8" cols="40">${data | h}</textarea>
</section>
</div>
<%include file="metadata-edit.html" />
<section class="raw-edit">
<textarea name="" class="edit-box" rows="8" cols="40">${data | h}</textarea>
</section>

View File

@@ -29,7 +29,6 @@
</ul>
</section>
<%include file="metadata-edit.html" />
<div class="content">
<section class="modules">
<ol>
@@ -50,5 +49,6 @@
</ol>
</section>
</div>
<%include file="metadata-edit.html" />
</section>

View File

@@ -28,4 +28,4 @@ class CmsNamespace(Namespace):
"""
published_date = DateTuple(help="Date when the module was published", scope=Scope.settings)
published_by = String(help="Id of the user who published this module", scope=Scope.settings)
empty = StringyBoolean(help="Whether this is an empty template", scope=Scope.settings, default=False)