diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 330eb0ee35..240a7dc2d4 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -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, diff --git a/cms/envs/test.py b/cms/envs/test.py index f33f0f3e76..4875b8447b 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -36,6 +36,7 @@ from lms.envs.test import ( MEDIA_URL, COMPREHENSIVE_THEME_DIRS, JWT_AUTH, + REGISTRATION_EXTRA_FIELDS, ) # mongo connection settings diff --git a/cms/templates/js/timed-examination-preference-editor.underscore b/cms/templates/js/timed-examination-preference-editor.underscore index a72e72996a..fad00198d3 100644 --- a/cms/templates/js/timed-examination-preference-editor.underscore +++ b/cms/templates/js/timed-examination-preference-editor.underscore @@ -42,7 +42,18 @@ -
+ <% var online_proctoring_rules = xblockInfo.get('online_proctoring_rules'); %> + diff --git a/common/djangoapps/third_party_auth/saml.py b/common/djangoapps/third_party_auth/saml.py index 9b4ea285cb..568e785920 100644 --- a/common/djangoapps/third_party_auth/saml.py +++ b/common/djangoapps/third_party_auth/saml.py @@ -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(): diff --git a/common/djangoapps/third_party_auth/tests/specs/base.py b/common/djangoapps/third_party_auth/tests/specs/base.py index 8c32eb8363..ffea3e0efa 100644 --- a/common/djangoapps/third_party_auth/tests/specs/base.py +++ b/common/djangoapps/third_party_auth/tests/specs/base.py @@ -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', diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py index 6224d48148..d80607309d 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py @@ -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): """ diff --git a/common/test/acceptance/pages/lms/learner_profile.py b/common/test/acceptance/pages/lms/learner_profile.py index 73751e0f30..91d1d6e255 100644 --- a/common/test/acceptance/pages/lms/learner_profile.py +++ b/common/test/acceptance/pages/lms/learner_profile.py @@ -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. diff --git a/common/test/acceptance/tests/lms/test_learner_profile.py b/common/test/acceptance/tests/lms/test_learner_profile.py index 95ef7d452b..f896cea2bb 100644 --- a/common/test/acceptance/tests/lms/test_learner_profile.py +++ b/common/test/acceptance/tests/lms/test_learner_profile.py @@ -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. diff --git a/lms/static/js/views/fields.js b/lms/static/js/views/fields.js index 6f87581da3..878333df28 100644 --- a/lms/static/js/views/fields.js +++ b/lms/static/js/views/fields.js @@ -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) : ''; diff --git a/lms/static/sass/_build-lms-v1.scss b/lms/static/sass/_build-lms-v1.scss index ddca292653..3a311a74e6 100644 --- a/lms/static/sass/_build-lms-v1.scss +++ b/lms/static/sass/_build-lms-v1.scss @@ -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'; diff --git a/lms/static/sass/elements/_pagination.scss b/lms/static/sass/elements/_pagination.scss index 21d3fea81f..fa907f93ea 100644 --- a/lms/static/sass/elements/_pagination.scss +++ b/lms/static/sass/elements/_pagination.scss @@ -74,7 +74,7 @@ margin: 0 ($baseline*0.75); padding: ($baseline/4); text-align: center; - color: $gray; + color: $gray-d1; } .current-page { diff --git a/lms/static/sass/views/_learner-profile.scss b/lms/static/sass/features/_learner-profile.scss similarity index 73% rename from lms/static/sass/views/_learner-profile.scss rename to lms/static/sass/features/_learner-profile.scss index 99ff1d9248..8a0226c168 100644 --- a/lms/static/sass/views/_learner-profile.scss +++ b/lms/static/sass/features/_learner-profile.scss @@ -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 { diff --git a/lms/static/sass/partials/base/_variables.scss b/lms/static/sass/partials/base/_variables.scss index 6ff97cec34..f15a9c7929 100644 --- a/lms/static/sass/partials/base/_variables.scss +++ b/lms/static/sass/partials/base/_variables.scss @@ -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; diff --git a/lms/static/sass/shared/_fields.scss b/lms/static/sass/shared/_fields.scss index 02e51ac95c..e2d3e8a022 100644 --- a/lms/static/sass/shared/_fields.scss +++ b/lms/static/sass/shared/_fields.scss @@ -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; diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 1e47a40a22..6eabedef18 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -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; diff --git a/lms/templates/fields/field_dropdown.underscore b/lms/templates/fields/field_dropdown.underscore index 097365c9dc..48826683a1 100644 --- a/lms/templates/fields/field_dropdown.underscore +++ b/lms/templates/fields/field_dropdown.underscore @@ -1,17 +1,17 @@ -<% if (editable !== 'never') { %> - <% if (title && titleVisible) { %> - - <% } else { %> - - <% } %> +<% if (title && titleVisible) { %> + +<% } else { %> + <% } %> -<% if (iconName) { %> - +<% if (editable !== 'never') { %> + <% if (iconName) { %> + + <% } %> <% } %> diff --git a/lms/templates/fields/field_dropdown_account.underscore b/lms/templates/fields/field_dropdown_account.underscore index 6bb8bef0ad..4fa6e5e458 100644 --- a/lms/templates/fields/field_dropdown_account.underscore +++ b/lms/templates/fields/field_dropdown_account.underscore @@ -1,14 +1,12 @@