Merge branch 'master' into EDUCATOR-926
This commit is contained in:
@@ -1194,6 +1194,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
elif xblock.category == 'sequential':
|
||||
xblock_info.update({
|
||||
'is_proctored_exam': xblock.is_proctored_exam,
|
||||
'online_proctoring_rules': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('online_proctoring_rules', {}),
|
||||
'is_practice_exam': xblock.is_practice_exam,
|
||||
'is_time_limited': xblock.is_time_limited,
|
||||
'exam_review_rules': xblock.exam_review_rules,
|
||||
|
||||
@@ -36,6 +36,7 @@ from lms.envs.test import (
|
||||
MEDIA_URL,
|
||||
COMPREHENSIVE_THEME_DIRS,
|
||||
JWT_AUTH,
|
||||
REGISTRATION_EXTRA_FIELDS,
|
||||
)
|
||||
|
||||
# mongo connection settings
|
||||
|
||||
@@ -42,7 +42,18 @@
|
||||
<textarea cols="50" maxlength="255" aria-describedby="review-rules-description"
|
||||
class="review-rules input input-text" autocomplete="off" />
|
||||
</label>
|
||||
<p class='field-message' id='review-rules-description'><%- gettext('Specify any additional rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed.') %></p>
|
||||
<% var online_proctoring_rules = xblockInfo.get('online_proctoring_rules'); %>
|
||||
<p class='field-message' id='review-rules-description'>
|
||||
<%= edx.HtmlUtils.interpolateHtml(
|
||||
gettext('Specify any rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed. These specified rules are visible to learners before the learners start the exam, along with the {linkStart}general proctored exam rules{linkEnd}.'),
|
||||
{
|
||||
linkStart: edx.HtmlUtils.interpolateHtml(
|
||||
edx.HtmlUtils.HTML('<a href="{onlineProctoringUrl}" title="{onlineProctoringTitle}">'),
|
||||
{ onlineProctoringUrl: online_proctoring_rules, onlineProctoringTitle: gettext('General Proctored Exam Rules')}),
|
||||
linkEnd: edx.HtmlUtils.HTML('</a>')
|
||||
})
|
||||
%>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import requests
|
||||
from django.contrib.sites.models import Site
|
||||
from django.http import Http404
|
||||
from django.utils.functional import cached_property
|
||||
from django_countries import countries
|
||||
from social_core.backends.saml import OID_EDU_PERSON_ENTITLEMENT, SAMLAuth, SAMLIdentityProvider
|
||||
from social_core.exceptions import AuthForbidden
|
||||
|
||||
@@ -134,6 +135,77 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
'odata_client_id',
|
||||
)
|
||||
|
||||
# Define the relationships between SAPSF record fields and Open edX logistration fields.
|
||||
default_field_mapping = {
|
||||
'username': 'username',
|
||||
'firstName': 'first_name',
|
||||
'lastName': 'last_name',
|
||||
'defaultFullName': 'fullname',
|
||||
'email': 'email',
|
||||
'country': 'country',
|
||||
'city': 'city',
|
||||
}
|
||||
|
||||
# Define a simple mapping to relate SAPSF values to Open edX-compatible values for
|
||||
# any given field. By default, this only contains the Country field, as SAPSF supplies
|
||||
# a country name, which has to be translated to a country code.
|
||||
default_value_mapping = {
|
||||
'country': {name: code for code, name in countries}
|
||||
}
|
||||
|
||||
# Unfortunately, not everything has a 1:1 name mapping between Open edX and SAPSF, so
|
||||
# we need some overrides. TODO: Fill in necessary mappings
|
||||
default_value_mapping.update({
|
||||
'United States': 'US',
|
||||
})
|
||||
|
||||
def get_registration_fields(self, response):
|
||||
"""
|
||||
Get a dictionary mapping registration field names to default values.
|
||||
"""
|
||||
field_mapping = self.field_mappings
|
||||
registration_fields = {edx_name: response['d'].get(odata_name, '') for odata_name, edx_name in field_mapping.items()}
|
||||
value_mapping = self.value_mappings
|
||||
for field, value in registration_fields.items():
|
||||
if field in value_mapping and value in value_mapping[field]:
|
||||
registration_fields[field] = value_mapping[field][value]
|
||||
return registration_fields
|
||||
|
||||
@property
|
||||
def field_mappings(self):
|
||||
"""
|
||||
Get a dictionary mapping the field names returned in an SAP SuccessFactors
|
||||
user entity to the field names with which those values should be used in
|
||||
the Open edX registration form.
|
||||
"""
|
||||
overrides = self.conf.get('sapsf_field_mappings', {})
|
||||
base = self.default_field_mapping.copy()
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
@property
|
||||
def value_mappings(self):
|
||||
"""
|
||||
Get a dictionary mapping of field names to override objects which each
|
||||
map values received from SAP SuccessFactors to values expected in the
|
||||
Open edX platform registration form.
|
||||
"""
|
||||
overrides = self.conf.get('sapsf_value_mappings', {})
|
||||
base = self.default_value_mapping.copy()
|
||||
for field, override in overrides.items():
|
||||
if field in base:
|
||||
base[field].update(override)
|
||||
else:
|
||||
base[field] = override[field]
|
||||
return base
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""
|
||||
The number of seconds OData API requests should wait for a response before failing.
|
||||
"""
|
||||
return self.conf.get('odata_api_request_timeout', 10)
|
||||
|
||||
@property
|
||||
def sapsf_idp_url(self):
|
||||
return self.conf['sapsf_oauth_root_url'] + 'idp'
|
||||
@@ -187,7 +259,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
'token_url': self.sapsf_token_url,
|
||||
'private_key': self.sapsf_private_key,
|
||||
},
|
||||
timeout=10,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
assertion.raise_for_status()
|
||||
assertion = assertion.text
|
||||
@@ -199,7 +271,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
'grant_type': 'urn:ietf:params:oauth:grant-type:saml2-bearer',
|
||||
'assertion': assertion,
|
||||
},
|
||||
timeout=10,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
token.raise_for_status()
|
||||
token = token.json()['access_token']
|
||||
@@ -220,12 +292,14 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
username = details['username']
|
||||
try:
|
||||
client = self.get_odata_api_client(user_id=username)
|
||||
fields = ','.join(self.field_mappings)
|
||||
response = client.get(
|
||||
'{root_url}User(userId=\'{user_id}\')?$select=username,firstName,lastName,defaultFullName,email'.format(
|
||||
'{root_url}User(userId=\'{user_id}\')?$select={fields}'.format(
|
||||
root_url=self.odata_api_root_url,
|
||||
user_id=username
|
||||
user_id=username,
|
||||
fields=fields,
|
||||
),
|
||||
timeout=10,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
response = response.json()
|
||||
@@ -237,13 +311,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
self.odata_company_id,
|
||||
)
|
||||
return details
|
||||
return {
|
||||
'username': response['d']['username'],
|
||||
'first_name': response['d']['firstName'],
|
||||
'last_name': response['d']['lastName'],
|
||||
'fullname': response['d']['defaultFullName'],
|
||||
'email': response['d']['email'],
|
||||
}
|
||||
return self.get_registration_fields(response)
|
||||
|
||||
|
||||
def get_saml_idp_choices():
|
||||
|
||||
@@ -54,7 +54,7 @@ class IntegrationTestMixin(object):
|
||||
self.addCleanup(patcher.stop)
|
||||
# Override this method in a subclass and enable at least one provider.
|
||||
|
||||
def test_register(self):
|
||||
def test_register(self, **extra_defaults):
|
||||
# The user goes to the register page, and sees a button to register with the provider:
|
||||
provider_register_url = self._check_register_page()
|
||||
# The user clicks on the Dummy button:
|
||||
@@ -76,6 +76,8 @@ class IntegrationTestMixin(object):
|
||||
self.assertEqual(form_fields['email']['defaultValue'], self.USER_EMAIL)
|
||||
self.assertEqual(form_fields['name']['defaultValue'], self.USER_NAME)
|
||||
self.assertEqual(form_fields['username']['defaultValue'], self.USER_USERNAME)
|
||||
for field_name, value in extra_defaults.items():
|
||||
self.assertEqual(form_fields[field_name]['defaultValue'], value)
|
||||
registration_values = {
|
||||
'email': 'email-edited@tpa-test.none',
|
||||
'name': 'My Customized Name',
|
||||
|
||||
@@ -309,6 +309,7 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes
|
||||
'lastName': 'Smith',
|
||||
'defaultFullName': 'John Smith',
|
||||
'email': 'john@smith.com',
|
||||
'country': 'Australia',
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -331,23 +332,119 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes
|
||||
self.USER_USERNAME = "myself"
|
||||
super(SuccessFactorsIntegrationTest, self).test_register()
|
||||
|
||||
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
|
||||
def test_register_sapsf_metadata_present(self):
|
||||
"""
|
||||
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
|
||||
API, and ensure that the data it gets that way gets passed to the registration form.
|
||||
|
||||
Check that value mappings overrides work in cases where we override a value other than
|
||||
what we're looking for, and when an empty override is provided (expected behavior is that
|
||||
existing value maps will be left alone).
|
||||
"""
|
||||
expected_country = 'AU'
|
||||
provider_settings = {
|
||||
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
|
||||
'sapsf_private_key': 'fake_private_key_here',
|
||||
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
|
||||
'odata_company_id': 'NCC1701D',
|
||||
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
|
||||
}
|
||||
|
||||
self._configure_testshib_provider(
|
||||
identity_provider_type='sap_success_factors',
|
||||
metadata_source=TESTSHIB_METADATA_URL,
|
||||
other_settings=json.dumps({
|
||||
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
|
||||
'sapsf_private_key': 'fake_private_key_here',
|
||||
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
|
||||
'odata_company_id': 'NCC1701D',
|
||||
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
|
||||
})
|
||||
other_settings=json.dumps(provider_settings)
|
||||
)
|
||||
super(SuccessFactorsIntegrationTest, self).test_register()
|
||||
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
|
||||
|
||||
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
|
||||
def test_register_sapsf_metadata_present_override_relevant_value(self):
|
||||
"""
|
||||
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
|
||||
API, and ensure that the data it gets that way gets passed to the registration form.
|
||||
|
||||
Check that value mappings overrides work in cases where we override a value other than
|
||||
what we're looking for, and when an empty override is provided (expected behavior is that
|
||||
existing value maps will be left alone).
|
||||
"""
|
||||
value_map = {'country': {'Australia': 'NZ'}}
|
||||
expected_country = 'NZ'
|
||||
provider_settings = {
|
||||
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
|
||||
'sapsf_private_key': 'fake_private_key_here',
|
||||
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
|
||||
'odata_company_id': 'NCC1701D',
|
||||
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
|
||||
}
|
||||
if value_map:
|
||||
provider_settings['sapsf_value_mappings'] = value_map
|
||||
|
||||
self._configure_testshib_provider(
|
||||
identity_provider_type='sap_success_factors',
|
||||
metadata_source=TESTSHIB_METADATA_URL,
|
||||
other_settings=json.dumps(provider_settings)
|
||||
)
|
||||
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
|
||||
|
||||
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
|
||||
def test_register_sapsf_metadata_present_override_other_value(self):
|
||||
"""
|
||||
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
|
||||
API, and ensure that the data it gets that way gets passed to the registration form.
|
||||
|
||||
Check that value mappings overrides work in cases where we override a value other than
|
||||
what we're looking for, and when an empty override is provided (expected behavior is that
|
||||
existing value maps will be left alone).
|
||||
"""
|
||||
value_map = {'country': {'United States': 'blahfake'}}
|
||||
expected_country = 'AU'
|
||||
provider_settings = {
|
||||
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
|
||||
'sapsf_private_key': 'fake_private_key_here',
|
||||
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
|
||||
'odata_company_id': 'NCC1701D',
|
||||
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
|
||||
}
|
||||
if value_map:
|
||||
provider_settings['sapsf_value_mappings'] = value_map
|
||||
|
||||
self._configure_testshib_provider(
|
||||
identity_provider_type='sap_success_factors',
|
||||
metadata_source=TESTSHIB_METADATA_URL,
|
||||
other_settings=json.dumps(provider_settings)
|
||||
)
|
||||
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
|
||||
|
||||
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
|
||||
def test_register_sapsf_metadata_present_empty_value_override(self):
|
||||
"""
|
||||
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
|
||||
API, and ensure that the data it gets that way gets passed to the registration form.
|
||||
|
||||
Check that value mappings overrides work in cases where we override a value other than
|
||||
what we're looking for, and when an empty override is provided (expected behavior is that
|
||||
existing value maps will be left alone).
|
||||
"""
|
||||
|
||||
value_map = {'country': {}}
|
||||
expected_country = 'AU'
|
||||
provider_settings = {
|
||||
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
|
||||
'sapsf_private_key': 'fake_private_key_here',
|
||||
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
|
||||
'odata_company_id': 'NCC1701D',
|
||||
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
|
||||
}
|
||||
if value_map:
|
||||
provider_settings['sapsf_value_mappings'] = value_map
|
||||
|
||||
self._configure_testshib_provider(
|
||||
identity_provider_type='sap_success_factors',
|
||||
metadata_source=TESTSHIB_METADATA_URL,
|
||||
other_settings=json.dumps(provider_settings)
|
||||
)
|
||||
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
|
||||
|
||||
def test_register_http_failure(self):
|
||||
"""
|
||||
|
||||
@@ -13,10 +13,6 @@ from common.test.acceptance.tests.helpers import select_option_by_value
|
||||
|
||||
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
|
||||
PROFILE_VISIBILITY_INPUT = '#u-field-select-account_privacy'
|
||||
FIELD_ICONS = {
|
||||
'country': 'fa-map-marker',
|
||||
'language_proficiencies': 'fa-comment',
|
||||
}
|
||||
|
||||
|
||||
class Badge(PageObject):
|
||||
@@ -214,18 +210,6 @@ class LearnerProfilePage(FieldsMixin, PageObject):
|
||||
self.wait_for_ajax()
|
||||
return self.q(css='#u-field-select-account_privacy').visible
|
||||
|
||||
def field_icon_present(self, field_id):
|
||||
"""
|
||||
Check if an icon is present for a field. Only dropdown fields have icons.
|
||||
|
||||
Arguments:
|
||||
field_id (str): field id
|
||||
|
||||
Returns:
|
||||
True/False
|
||||
"""
|
||||
return self.icon_for_field(field_id, FIELD_ICONS[field_id])
|
||||
|
||||
def wait_for_public_fields(self):
|
||||
"""
|
||||
Wait for `country`, `language` and `bio` fields to be visible.
|
||||
|
||||
@@ -367,8 +367,6 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, AcceptanceTest):
|
||||
profile_page.make_field_editable('country')
|
||||
self.assertEqual(profile_page.mode_for_field('country'), 'edit')
|
||||
|
||||
self.assertTrue(profile_page.field_icon_present('country'))
|
||||
|
||||
def test_language_field(self):
|
||||
"""
|
||||
Test behaviour of `Language` field.
|
||||
@@ -396,8 +394,6 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, AcceptanceTest):
|
||||
profile_page.make_field_editable('language_proficiencies')
|
||||
self.assertTrue(profile_page.mode_for_field('language_proficiencies'), 'edit')
|
||||
|
||||
self.assertTrue(profile_page.field_icon_present('language_proficiencies'))
|
||||
|
||||
def test_about_me_field(self):
|
||||
"""
|
||||
Test behaviour of `About Me` field.
|
||||
|
||||
@@ -515,15 +515,16 @@
|
||||
'click .wrapper-u-field': 'startEditing',
|
||||
'click .u-field-placeholder': 'startEditing',
|
||||
'focusout textarea': 'finishEditing',
|
||||
'change textarea': 'adjustTextareaHeight',
|
||||
'keyup textarea': 'adjustTextareaHeight',
|
||||
'change textarea': 'manageTextareaContentChange',
|
||||
'keyup textarea': 'manageTextareaContentChange',
|
||||
'keydown textarea': 'onKeyDown',
|
||||
'paste textarea': 'adjustTextareaHeight',
|
||||
'cut textarea': 'adjustTextareaHeight'
|
||||
'paste textarea': 'manageTextareaContentChange',
|
||||
'cut textarea': 'manageTextareaContentChange'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
_.bindAll(this, 'render', 'onKeyDown', 'adjustTextareaHeight', 'fieldValue', 'saveValue', 'updateView');
|
||||
_.bindAll(this, 'render', 'onKeyDown', 'adjustTextareaHeight', 'manageTextareaContentChange',
|
||||
'fieldValue', 'saveValue', 'updateView');
|
||||
this._super(options);
|
||||
this.listenTo(this.model, 'change:' + this.options.valueAttribute, this.updateView);
|
||||
},
|
||||
@@ -541,7 +542,8 @@
|
||||
value: value,
|
||||
message: this.helpMessage,
|
||||
messagePosition: this.options.messagePosition || 'footer',
|
||||
placeholderValue: this.options.placeholderValue
|
||||
placeholderValue: this.options.placeholderValue,
|
||||
maxCharacters: this.options.maxCharacters || ''
|
||||
}));
|
||||
this.delegateEvents();
|
||||
this.title((this.modelValue() || this.mode === 'edit') ?
|
||||
@@ -562,12 +564,26 @@
|
||||
}
|
||||
},
|
||||
|
||||
updateCharCount: function() {
|
||||
var curCharCount;
|
||||
// Update character count for textarea
|
||||
if (this.options.maxCharacters) {
|
||||
curCharCount = $('#u-field-textarea-' + this.options.valueAttribute).val().length;
|
||||
$('.u-field-footer .current-char-count').text(curCharCount);
|
||||
}
|
||||
},
|
||||
|
||||
adjustTextareaHeight: function() {
|
||||
if (this.persistChanges === false) { return; }
|
||||
var textarea = this.$('textarea');
|
||||
textarea.css('height', 'auto').css('height', textarea.prop('scrollHeight') + 10);
|
||||
},
|
||||
|
||||
manageTextareaContentChange: function() {
|
||||
this.updateCharCount();
|
||||
this.adjustTextareaHeight();
|
||||
},
|
||||
|
||||
modelValue: function() {
|
||||
var value = this._super();
|
||||
return value ? $.trim(value) : '';
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
|
||||
// base - specific views
|
||||
@import "views/account-settings";
|
||||
@import "views/learner-profile";
|
||||
@import 'views/login-register';
|
||||
@import 'views/verification';
|
||||
@import 'views/decoupled-verification';
|
||||
@@ -69,6 +68,7 @@
|
||||
|
||||
// features
|
||||
@import 'features/bookmarks-v1';
|
||||
@import 'features/learner-profile';
|
||||
|
||||
// search
|
||||
@import 'search/search';
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
margin: 0 ($baseline*0.75);
|
||||
padding: ($baseline/4);
|
||||
text-align: center;
|
||||
color: $gray;
|
||||
color: $gray-d1;
|
||||
}
|
||||
|
||||
.current-page {
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
}
|
||||
|
||||
.profile-image-field {
|
||||
@include float(left);
|
||||
|
||||
button {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
@@ -41,13 +39,18 @@
|
||||
.image-wrapper {
|
||||
width: $profile-image-dimension;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
|
||||
.image-frame {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: $profile-image-dimension;
|
||||
height: $profile-image-dimension;
|
||||
border-radius: ($baseline/4);
|
||||
border-radius: ($profile-image-dimension/2);
|
||||
overflow: hidden;
|
||||
border: 3px solid $gray-lightest;
|
||||
margin-top: $baseline*-0.75;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.u-field-upload-button {
|
||||
@@ -55,13 +58,12 @@
|
||||
top: 0;
|
||||
width: $profile-image-dimension;
|
||||
height: $profile-image-dimension;
|
||||
border-radius: ($baseline/4);
|
||||
border-radius: ($profile-image-dimension/2);
|
||||
border: 2px dashed transparent;
|
||||
background: rgba(229,241,247, .8);
|
||||
color: $link-color;
|
||||
text-shadow: none;
|
||||
@include transition(all $tmg-f1 ease-in-out 0s);
|
||||
opacity: 0;
|
||||
z-index: 6;
|
||||
|
||||
i {
|
||||
@@ -87,17 +89,20 @@
|
||||
line-height: 1.3em;
|
||||
text-align: center;
|
||||
z-index: 7;
|
||||
color: $base-font-color;
|
||||
}
|
||||
|
||||
.upload-button-input {
|
||||
position: absolute;
|
||||
top: -($profile-image-dimension * 2);
|
||||
top: 0;
|
||||
@include left(0);
|
||||
width: $profile-image-dimension;
|
||||
border-radius: ($profile-image-dimension/2);
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
z-index: 5;
|
||||
outline: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.u-field-remove-button {
|
||||
@@ -113,6 +118,7 @@
|
||||
|
||||
.wrapper-profile {
|
||||
min-height: 200px;
|
||||
background-color: $gray-lightest;
|
||||
|
||||
.ui-loading-indicator {
|
||||
margin-top: 100px;
|
||||
@@ -133,7 +139,7 @@
|
||||
@extend .container;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0 ($baseline*1.5);
|
||||
padding: 0 ($baseline*3);
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
@@ -164,53 +170,93 @@
|
||||
|
||||
.wrapper-profile-sections {
|
||||
@extend .container;
|
||||
padding: 0 ($baseline*1.5);
|
||||
@include padding($baseline*1.5, $baseline*1.5, $baseline*1.5, 0);
|
||||
min-width: 0;
|
||||
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
@include margin-left(0);
|
||||
@include padding($baseline*1.5, 0, $baseline*1.5, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
@include padding(0, $baseline*2, $baseline, $baseline*3);
|
||||
|
||||
.header {
|
||||
@extend %t-title4;
|
||||
@extend %t-ultrastrong;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.subheader {
|
||||
@extend %t-title6;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile-section-one {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
margin-top: ($baseline*1.5);
|
||||
@include margin-left($baseline/2);
|
||||
@include float(left);
|
||||
@include margin-left($baseline*3);
|
||||
width: 300px;
|
||||
background-color: $white;
|
||||
border-top: 5px solid $blue;
|
||||
padding-bottom: $baseline;
|
||||
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
@include margin-left(0);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-section-one-fields {
|
||||
@include float(left);
|
||||
width: flex-grid(4, 12);
|
||||
@include margin-left($baseline);
|
||||
margin: 0 $baseline/2;
|
||||
|
||||
.u-field {
|
||||
margin-bottom: ($baseline/4);
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
@include padding-left(3px);
|
||||
}
|
||||
|
||||
.u-field-username {
|
||||
|
||||
input[type="text"] {
|
||||
font-weight: 600;
|
||||
}
|
||||
@extend %t-weight4;
|
||||
@include padding(0, 0, 0, 3px);
|
||||
color: $base-font-color;
|
||||
|
||||
.u-field-value {
|
||||
width: 350px;
|
||||
@extend %t-title4;
|
||||
@extend %t-weight4;
|
||||
width: calc(100% - 40px);
|
||||
|
||||
.u-field-value-readonly {
|
||||
@extend %t-weight3;
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
color: $base-font-color;
|
||||
font-size: $body-font-size;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.u-field-dropdown {
|
||||
margin-top: $baseline/5;
|
||||
|
||||
&:not(.editable-never) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: $baseline/4;
|
||||
border-bottom: 1px solid $gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-icon {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
&>.u-field {
|
||||
&:not(:first-child) {
|
||||
font-size: $body-font-size;
|
||||
color: $base-font-color;
|
||||
font-weight: $font-light;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
width: 200px;
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
&:first-child {
|
||||
@extend %t-title4;
|
||||
@extend %t-weight4;
|
||||
font-size: em(24);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -230,16 +276,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile-section-two {
|
||||
padding-top: 1em;
|
||||
width: flex-grid(8, 12);
|
||||
}
|
||||
.wrapper-profile-section-container-two {
|
||||
@include float(left);
|
||||
width: calc(100% - 380px);
|
||||
max-width: $learner-profile-container-flex; // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
|
||||
.profile-section-two-fields {
|
||||
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
|
||||
width: 100%;
|
||||
margin-top: $baseline;
|
||||
}
|
||||
|
||||
.u-field-textarea {
|
||||
margin-bottom: ($baseline/2);
|
||||
padding: ($baseline/2) ($baseline*.75) ($baseline*.75) ($baseline*.75);
|
||||
padding: 0 ($baseline*.75) ($baseline*.75) ($baseline*.75);
|
||||
|
||||
.u-field-header {
|
||||
position: relative;
|
||||
|
||||
.u-field-message {
|
||||
@include right(0);
|
||||
top: $baseline/4;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&.editable-toggle {
|
||||
cursor: pointer;
|
||||
@@ -247,22 +306,30 @@
|
||||
}
|
||||
|
||||
.u-field-title {
|
||||
@extend %t-title5;
|
||||
@extend %t-weight4;
|
||||
@extend %t-title6;
|
||||
@extend %t-weight5;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
margin-bottom: ($baseline/4);
|
||||
color: inherit;
|
||||
color: $gray-dark;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.u-field-value {
|
||||
@extend %t-copy-base;
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border-radius: 5px;
|
||||
border-color: $gray-d1;
|
||||
resize: none;
|
||||
white-space: pre-line;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -273,16 +340,22 @@
|
||||
.u-field-message {
|
||||
@include float(right);
|
||||
width: auto;
|
||||
|
||||
.message-can-edit {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.u-field.mode-placeholder {
|
||||
padding: $baseline;
|
||||
margin: $baseline * 0.75;
|
||||
border: 2px dashed $gray-l3;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
@include padding-right(5px);
|
||||
vertical-align: middle;
|
||||
color: $gray;
|
||||
color: $base-font-color;
|
||||
}
|
||||
.u-field-title {
|
||||
width: 100%;
|
||||
@@ -293,7 +366,7 @@
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
@extend %t-copy-sub1;
|
||||
color: $gray;
|
||||
color: $base-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +377,28 @@
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-u-field {
|
||||
font-size: $body-font-size;
|
||||
color: $base-font-color;
|
||||
|
||||
.u-field-header .u-field-title{
|
||||
color: $base-font-color;
|
||||
}
|
||||
|
||||
.u-field-footer {
|
||||
.field-textarea-character-count {
|
||||
@extend %t-weight1;
|
||||
@include float(right);
|
||||
margin-top: $baseline/4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-private-message {
|
||||
@include padding-left($baseline*0.75);
|
||||
line-height: 3.0em;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-paging-header {
|
||||
@@ -223,6 +223,9 @@ $success-color-hover: rgb(0, 129, 0) !default;
|
||||
// ----------------------------
|
||||
// #COLORS- Bootstrap-style
|
||||
// ----------------------------
|
||||
$gray-dark: #4e5455 !default;
|
||||
$gray-lighter: #eceeef !default;
|
||||
$gray-lightest: #f7f7f9 !default;
|
||||
|
||||
$state-success-text: $black !default;
|
||||
$state-success-bg: #dff0d8 !default;
|
||||
@@ -545,6 +548,9 @@ $palette-success-border: #b9edb9;
|
||||
$palette-success-back: #ecfaec;
|
||||
$palette-success-text: #008100;
|
||||
|
||||
// learner profile elements
|
||||
$learner-profile-container-flex: 768px;
|
||||
|
||||
// course elements
|
||||
$content-wrapper-bg: $white !default;
|
||||
$course-bg-color: $uxpl-grayscale-x-back !default;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
border-radius: 3px;
|
||||
|
||||
span {
|
||||
color: $gray;
|
||||
color: $gray-d1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -84,7 +84,7 @@
|
||||
.u-field-title {
|
||||
width: flex-grid(3, 12);
|
||||
display: inline-block;
|
||||
color: $gray;
|
||||
color: $gray-d1;
|
||||
vertical-align: top;
|
||||
margin-bottom: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.header-global {
|
||||
@extend %ui-depth1;
|
||||
border-bottom: 2px solid $header-border-color;
|
||||
border-bottom: 1px solid $header-border-color;
|
||||
box-shadow: 0 1px 5px 0 $shadow-l1;
|
||||
background: $header-bg;
|
||||
position: relative;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<% if (editable !== 'never') { %>
|
||||
<% if (title && titleVisible) { %>
|
||||
<label class="u-field-title" for="u-field-select-<%- id %>">
|
||||
<%- title %>
|
||||
</label>
|
||||
<% } else { %>
|
||||
<label class="sr" for="u-field-select-<%- id %>">
|
||||
<%- screenReaderTitle %>
|
||||
</label>
|
||||
<% } %>
|
||||
<% if (title && titleVisible) { %>
|
||||
<label class="u-field-title" for="u-field-select-<%- id %>">
|
||||
<%- title %>
|
||||
</label>
|
||||
<% } else { %>
|
||||
<label class="sr" for="u-field-select-<%- id %>">
|
||||
<%- screenReaderTitle %>
|
||||
</label>
|
||||
<% } %>
|
||||
|
||||
<% if (iconName) { %>
|
||||
<span class="u-field-icon icon fa <%- iconName %> fa-fw" aria-hidden="true"></span>
|
||||
<% if (editable !== 'never') { %>
|
||||
<% if (iconName) { %>
|
||||
<span class="u-field-icon icon fa <%- iconName %> fa-fw" aria-hidden="true"></span>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<span class="u-field-value">
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<div class="u-field-value field">
|
||||
<% if (editable !== 'never') { %>
|
||||
<% if (title && titleVisible) { %>
|
||||
<label class="u-field-title field-label" for="u-field-select-<%- id %>">
|
||||
<%- title %>
|
||||
</label>
|
||||
<% } else { %>
|
||||
<label class="sr" for="u-field-select-<%- id %>">
|
||||
<%- screenReaderTitle %>
|
||||
</label>
|
||||
<% } %>
|
||||
<% if (title && titleVisible) { %>
|
||||
<label class="u-field-title field-label" for="u-field-select-<%- id %>">
|
||||
<%- title %>
|
||||
</label>
|
||||
<% } else { %>
|
||||
<label class="sr" for="u-field-select-<%- id %>">
|
||||
<%- screenReaderTitle %>
|
||||
</label>
|
||||
<% } %>
|
||||
|
||||
<% if (iconName) { %>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<% if (title) { %>
|
||||
<span class="u-field-title" aria-hidden="true"><%- title %></span>
|
||||
<span class="u-field-title" aria-hidden="true"><%- title %></span>
|
||||
<% } %>
|
||||
|
||||
<span class="sr" for="u-field-value-<%- id %>"><%- screenReaderTitle %></span>
|
||||
<span class="u-field-value" id="u-field-value-<%- id %>" aria-describedby="u-field-message-<%- id %>"><%- value %></span>
|
||||
<span class="u-field-message" id="u-field-message-<%- id %>">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="wrapper-u-field">
|
||||
<div class="wrapper-u-field" role="group">
|
||||
<div class="u-field-header">
|
||||
<% if (mode === 'edit') { %>
|
||||
<label class="u-field-title" for="u-field-textarea-<%- id %>" id="u-field-title-<%- id %>"></label>
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<div class="u-field-value" id="u-field-value-<%- id %>"
|
||||
<% if (mode === 'edit') { %>
|
||||
aria-labelledby="u-field-title-<%- id %>"><textarea id="u-field-textarea-<%- id %>" rows="4"
|
||||
aria-labelledby="u-field-title-<%- id %>"><textarea maxlength="<%- maxCharacters%>" id="u-field-textarea-<%- id %>" rows="4"
|
||||
<% if (message) { %>
|
||||
aria-describedby="u-field-message-help-<%- id %>"
|
||||
<% } %>
|
||||
@@ -43,5 +43,20 @@
|
||||
<span class="u-field-message-help" id="u-field-message-help-<%- id %>"> <%- message %></span>
|
||||
</span>
|
||||
<% } %>
|
||||
<% if (mode === 'edit' && maxCharacters) { %>
|
||||
<div class="field-textarea-character-count">
|
||||
<%=
|
||||
HtmlUtils.interpolateHtml(
|
||||
gettext('{currentCountOpeningTag}{currentCharacterCount}{currentCountClosingTag} of {maxCharacters}'),
|
||||
{
|
||||
currentCountOpeningTag: HtmlUtils.HTML('<span class="current-char-count">'),
|
||||
currentCountClosingTag: HtmlUtils.HTML('</span>'),
|
||||
currentCharacterCount: value.length,
|
||||
maxCharacters: maxCharacters
|
||||
}
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a class="btn btn-brand action-resume-course" href="${resume_course_url}">
|
||||
Start Course
|
||||
<a class="btn btn-brand action-resume-course" href="/courses/course-v1:edX+DemoX+Demo_Course/courseware/19a30717eff543078a5d94ae9d6c18a5/">
|
||||
<span data-action-type="start">Start Course</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
|
||||
export class CourseHome { // eslint-disable-line import/prefer-default-export
|
||||
constructor(options) {
|
||||
// Logging for 'Resume Course' or 'Start Course' button click
|
||||
const $resumeCourseLink = $(options.resumeCourseLink);
|
||||
$resumeCourseLink.on('click', (event) => {
|
||||
const eventType = $resumeCourseLink.find('span').data('action-type');
|
||||
Logger.log(
|
||||
'edx.course.home.resume_course.clicked',
|
||||
{
|
||||
event_type: eventType,
|
||||
url: event.currentTarget.href,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Logging for course tool click events
|
||||
const $courseToolLink = $(options.courseToolLink);
|
||||
$courseToolLink.on('click', (event) => {
|
||||
|
||||
@@ -9,11 +9,24 @@ describe('Course Home factory', () => {
|
||||
beforeEach(() => {
|
||||
loadFixtures('course_experience/fixtures/course-home-fragment.html');
|
||||
home = new CourseHome({
|
||||
resumeCourseLink: '.action-resume-course',
|
||||
courseToolLink: '.course-tool-link',
|
||||
});
|
||||
spyOn(Logger, 'log');
|
||||
});
|
||||
|
||||
it('sends an event when resume or start course is clicked', () => {
|
||||
$('.action-resume-course').click();
|
||||
expect(Logger.log).toHaveBeenCalledWith(
|
||||
'edx.course.home.resume_course.clicked',
|
||||
{
|
||||
event_type: 'start',
|
||||
url: `http://${window.location.host}/courses/course-v1:edX+DemoX+Demo_Course/courseware` +
|
||||
'/19a30717eff543078a5d94ae9d6c18a5/',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('sends an event when an course tool is clicked', () => {
|
||||
const courseToolNames = document.querySelectorAll('.course-tool-link');
|
||||
for (let i = 0; i < courseToolNames.length; i += 1) {
|
||||
|
||||
@@ -45,9 +45,9 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
|
||||
% if resume_course_url:
|
||||
<a class="btn btn-brand action-resume-course" href="${resume_course_url}">
|
||||
% if has_visited_course:
|
||||
${_("Resume Course")}
|
||||
<span data-action-type="resume">${_("Resume Course")}</span>
|
||||
% else:
|
||||
${_("Start Course")}
|
||||
<span data-action-type="start">${_("Start Course")}</span>
|
||||
% endif
|
||||
</a>
|
||||
% endif
|
||||
@@ -109,6 +109,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
|
||||
|
||||
<%static:webpack entry="CourseHome">
|
||||
new CourseHome({
|
||||
resumeCourseLink: ".action-resume-course",
|
||||
courseToolLink: ".course-tool-link",
|
||||
});
|
||||
</%static:webpack>
|
||||
|
||||
@@ -114,11 +114,8 @@ class CourseHomeMessageFragmentView(EdxFragmentView):
|
||||
CourseHomeMessages.register_info_message(
|
||||
request,
|
||||
Text(_(
|
||||
"{add_reminder_open_tag}Don't forget to add a calendar reminder!{add_reminder_close_tag}."
|
||||
)).format(
|
||||
add_reminder_open_tag='',
|
||||
add_reminder_close_tag=''
|
||||
),
|
||||
"Don't forget to add a calendar reminder!"
|
||||
)),
|
||||
title=Text("Course starts in {days_until_start_string} on {course_start_date}.").format(
|
||||
days_until_start_string=course_start_data['days_until_start_string'],
|
||||
course_start_date=course_start_data['course_start_date']
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
var accountPrivacyFieldView,
|
||||
profileImageFieldView,
|
||||
usernameFieldView,
|
||||
nameFieldView,
|
||||
sectionOneFieldViews,
|
||||
sectionTwoFieldViews,
|
||||
BadgeCollection,
|
||||
@@ -65,10 +66,7 @@
|
||||
required: true,
|
||||
editable: 'always',
|
||||
showMessages: false,
|
||||
title: StringUtils.interpolate(
|
||||
gettext('{platform_name} learners can see my:'),
|
||||
{platform_name: options.platform_name}
|
||||
),
|
||||
title: gettext('Profile Visibility:'),
|
||||
valueAttribute: 'account_privacy',
|
||||
options: [
|
||||
['private', gettext('Limited Profile')],
|
||||
@@ -97,29 +95,37 @@
|
||||
helpMessage: ''
|
||||
});
|
||||
|
||||
nameFieldView = new FieldsView.ReadonlyFieldView({
|
||||
model: accountSettingsModel,
|
||||
screenReaderTitle: gettext('Full Name'),
|
||||
valueAttribute: 'name',
|
||||
helpMessage: ''
|
||||
});
|
||||
|
||||
sectionOneFieldViews = [
|
||||
new FieldsView.DropdownFieldView({
|
||||
title: gettext('Location'),
|
||||
titleVisible: true,
|
||||
model: accountSettingsModel,
|
||||
screenReaderTitle: gettext('Country'),
|
||||
titleVisible: false,
|
||||
required: true,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-map-marker',
|
||||
placeholderValue: gettext('Add Country'),
|
||||
valueAttribute: 'country',
|
||||
options: options.country_options,
|
||||
helpMessage: '',
|
||||
persistChanges: true
|
||||
}),
|
||||
|
||||
new AccountSettingsFieldViews.LanguageProficienciesFieldView({
|
||||
title: gettext('Language'),
|
||||
titleVisible: true,
|
||||
model: accountSettingsModel,
|
||||
screenReaderTitle: gettext('Preferred Language'),
|
||||
titleVisible: false,
|
||||
required: false,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-comment',
|
||||
placeholderValue: gettext('Add language'),
|
||||
valueAttribute: 'language_proficiencies',
|
||||
options: options.language_options,
|
||||
@@ -139,7 +145,8 @@
|
||||
valueAttribute: 'bio',
|
||||
helpMessage: '',
|
||||
persistChanges: true,
|
||||
messagePosition: 'header'
|
||||
messagePosition: 'header',
|
||||
maxCharacters: 300
|
||||
})
|
||||
];
|
||||
|
||||
@@ -172,9 +179,11 @@
|
||||
accountPrivacyFieldView: accountPrivacyFieldView,
|
||||
profileImageFieldView: profileImageFieldView,
|
||||
usernameFieldView: usernameFieldView,
|
||||
nameFieldView: nameFieldView,
|
||||
sectionOneFieldViews: sectionOneFieldViews,
|
||||
sectionTwoFieldViews: sectionTwoFieldViews,
|
||||
badgeListContainer: badgeListContainer
|
||||
badgeListContainer: badgeListContainer,
|
||||
platformName: options.platform_name
|
||||
});
|
||||
|
||||
getProfileVisibility = function() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable vars-on-top */
|
||||
define(
|
||||
[
|
||||
'gettext',
|
||||
'backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
@@ -18,7 +19,7 @@ define(
|
||||
'js/student_account/views/account_settings_fields',
|
||||
'js/views/message_banner'
|
||||
],
|
||||
function(Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers,
|
||||
function(gettext, Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers,
|
||||
FieldViews, UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView,
|
||||
BadgeListContainer, AccountSettingsFieldViews, MessageBannerView) {
|
||||
'use strict';
|
||||
@@ -73,13 +74,19 @@ define(
|
||||
helpMessage: ''
|
||||
});
|
||||
|
||||
var nameFieldView = new FieldViews.ReadonlyFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: 'name',
|
||||
helpMessage: ''
|
||||
});
|
||||
|
||||
var sectionOneFieldViews = [
|
||||
new FieldViews.DropdownFieldView({
|
||||
title: gettext('Location'),
|
||||
model: accountSettingsModel,
|
||||
required: false,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-map-marker',
|
||||
placeholderValue: '',
|
||||
valueAttribute: 'country',
|
||||
options: Helpers.FIELD_OPTIONS,
|
||||
@@ -87,11 +94,11 @@ define(
|
||||
}),
|
||||
|
||||
new AccountSettingsFieldViews.LanguageProficienciesFieldView({
|
||||
title: gettext('Language'),
|
||||
model: accountSettingsModel,
|
||||
required: false,
|
||||
editable: editable,
|
||||
showMessages: false,
|
||||
iconName: 'fa-comment',
|
||||
placeholderValue: 'Add language',
|
||||
valueAttribute: 'language_proficiencies',
|
||||
options: Helpers.FIELD_OPTIONS,
|
||||
@@ -131,6 +138,7 @@ define(
|
||||
preferencesModel: accountPreferencesModel,
|
||||
accountPrivacyFieldView: accountPrivacyFieldView,
|
||||
usernameFieldView: usernameFieldView,
|
||||
nameFieldView: nameFieldView,
|
||||
profileImageFieldView: profileImageFieldView,
|
||||
sectionOneFieldViews: sectionOneFieldViews,
|
||||
sectionTwoFieldViews: sectionTwoFieldViews,
|
||||
|
||||
@@ -65,7 +65,7 @@ define(
|
||||
view.render();
|
||||
var bio = view.$el.find('.u-field-bio');
|
||||
expect(bio.length).toBe(0);
|
||||
var msg = view.$el.find('span.profile-private--message');
|
||||
var msg = view.$el.find('span.profile-private-message');
|
||||
expect(msg.length).toBe(1);
|
||||
expect(_.count(msg.html(), messageString)).toBeTruthy();
|
||||
};
|
||||
|
||||
@@ -41,11 +41,12 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
var expectSectionOneTobeRendered = function(learnerProfileView) {
|
||||
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
|
||||
|
||||
expect(sectionOneFieldElements.length).toBe(4);
|
||||
expect(sectionOneFieldElements.length).toBe(5);
|
||||
expectProfileElementContainsField(sectionOneFieldElements[0], learnerProfileView.options.profileImageFieldView);
|
||||
expectProfileElementContainsField(sectionOneFieldElements[1], learnerProfileView.options.usernameFieldView);
|
||||
expectProfileElementContainsField(sectionOneFieldElements[2], learnerProfileView.options.nameFieldView);
|
||||
|
||||
_.each(_.rest(sectionOneFieldElements, 2), function(sectionFieldElement, fieldIndex) {
|
||||
_.each(_.rest(sectionOneFieldElements, 3), function(sectionFieldElement, fieldIndex) {
|
||||
expectProfileElementContainsField(
|
||||
sectionFieldElement,
|
||||
learnerProfileView.options.sectionOneFieldViews[fieldIndex]
|
||||
@@ -89,10 +90,10 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
);
|
||||
|
||||
if (othersProfile) {
|
||||
expect($('.profile-private--message').text())
|
||||
expect($('.profile-private-message').text())
|
||||
.toBe('This learner is currently sharing a limited profile.');
|
||||
} else {
|
||||
expect($('.profile-private--message').text()).toBe('You are currently sharing a limited profile.');
|
||||
expect($('.profile-private-message').text()).toBe('You are currently sharing a limited profile.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
'gettext', 'jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/html-utils',
|
||||
'common/js/components/views/tabbed_view',
|
||||
'learner_profile/js/views/section_two_tab',
|
||||
'text!learner_profile/templates/learner_profile.underscore'
|
||||
'text!learner_profile/templates/learner_profile.underscore',
|
||||
'edx-ui-toolkit/js/utils/string-utils'
|
||||
],
|
||||
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab, learnerProfileTemplate) {
|
||||
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab, learnerProfileTemplate, StringUtils) {
|
||||
var LearnerProfileView = Backbone.View.extend({
|
||||
|
||||
initialize: function(options) {
|
||||
@@ -53,10 +54,19 @@
|
||||
ownProfile: this.options.ownProfile
|
||||
});
|
||||
|
||||
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(learnerProfileTemplate)({
|
||||
username: self.options.accountSettingsModel.get('username'),
|
||||
name: self.options.accountSettingsModel.get('name'),
|
||||
ownProfile: self.options.ownProfile,
|
||||
showFullProfile: self.showFullProfile()
|
||||
showFullProfile: self.showFullProfile(),
|
||||
profile_header: gettext('My Profile'),
|
||||
profile_subheader:
|
||||
StringUtils.interpolate(
|
||||
gettext('Build out your profile to personalize your identity on {platform_name}.'), {
|
||||
platform_name: self.options.platformName
|
||||
}
|
||||
)
|
||||
}));
|
||||
this.renderFields();
|
||||
|
||||
@@ -98,7 +108,7 @@
|
||||
Backbone.history.start();
|
||||
}
|
||||
} else {
|
||||
this.$el.find('.account-settings-container').append(this.sectionTwoView.render().el);
|
||||
this.$el.find('.wrapper-profile-section-container-two').append(this.sectionTwoView.render().el);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
@@ -120,6 +130,10 @@
|
||||
fieldView.delegateEvents();
|
||||
}
|
||||
|
||||
// Do not show name when in limited mode or no name has been set
|
||||
if (this.showFullProfile() && this.options.accountSettingsModel.get('name')) {
|
||||
this.$('.profile-section-one-fields').append(this.options.nameFieldView.render().el);
|
||||
}
|
||||
this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el);
|
||||
|
||||
imageView = this.options.profileImageFieldView;
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
<div class="profile <%- ownProfile ? 'profile-self' : 'profile-other' %>">
|
||||
<div class="wrapper-profile-field-account-privacy"></div>
|
||||
<div class="wrapper-profile-sections account-settings-container">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-image-field">
|
||||
<% if (ownProfile) { %>
|
||||
<div class="profile-header">
|
||||
<div class="header"> <%- profile_header %></div>
|
||||
<div class="subheader"> <%- profile_subheader %></div>
|
||||
</div>
|
||||
<div class="profile-section-one-fields">
|
||||
<% } %>
|
||||
<div class="wrapper-profile-section-container-one">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-image-field">
|
||||
</div>
|
||||
<div class="profile-section-one-fields">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy"><%- gettext("An error occurred. Try loading the page again.") %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-loading-error is-hidden">
|
||||
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
|
||||
<span class="copy"><%- gettext("An error occurred. Try loading the page again.") %></span>
|
||||
<div class="wrapper-profile-section-container-two">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div class="field-container"></div>
|
||||
<% if (!showFullProfile) { %>
|
||||
<% if(ownProfile) { %>
|
||||
<span class="profile-private--message" tabindex="0"><%- gettext("You are currently sharing a limited profile.") %></span>
|
||||
<span class="profile-private-message"><%- gettext("You are currently sharing a limited profile.") %></span>
|
||||
<% } else { %>
|
||||
<span class="profile-private--message" tabindex="0"><%- gettext("This learner is currently sharing a limited profile.") %></span>
|
||||
<span class="profile-private-message"><%- gettext("This learner is currently sharing a limited profile.") %></span>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
# For Harvard courses:
|
||||
-e git+https://github.com/gsehub/xblock-mentoring.git@4d1cce78dc232d5da6ffd73817b5c490e87a6eee#egg=xblock-mentoring
|
||||
git+https://github.com/open-craft/problem-builder.git@v2.6.10#egg=xblock-problem-builder==2.6.10
|
||||
git+https://github.com/open-craft/problem-builder.git@v2.6.5#egg=xblock-problem-builder==2.6.5
|
||||
|
||||
# Oppia XBlock
|
||||
-e git+https://github.com/oppia/xblock.git@9f6b95b7eb7dbabb96b77198a3202604f96adf65#egg=oppia-xblock
|
||||
|
||||
Reference in New Issue
Block a user