TNL-7303 Add Course License details (#103)
This commit is contained in:
166
src/courseware/course/course-license/CourseLicense.jsx
Normal file
166
src/courseware/course/course-license/CourseLicense.jsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCopyright } from '@fortawesome/free-regular-svg-icons';
|
||||
import {
|
||||
faCreativeCommons,
|
||||
faCreativeCommonsBy,
|
||||
faCreativeCommonsNc,
|
||||
faCreativeCommonsNd,
|
||||
faCreativeCommonsSa,
|
||||
faCreativeCommonsZero,
|
||||
} from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const CreativeCommonsLicenseTags = {
|
||||
by: {
|
||||
intlMessagesId: 'learn.course.license.creativeCommons.terms.by',
|
||||
icon: faCreativeCommonsBy,
|
||||
},
|
||||
nc: {
|
||||
intlMessagesId: 'learn.course.license.creativeCommons.terms.nc',
|
||||
icon: faCreativeCommonsNc,
|
||||
},
|
||||
nd: {
|
||||
intlMessagesId: 'learn.course.license.creativeCommons.terms.nd',
|
||||
icon: faCreativeCommonsNd,
|
||||
},
|
||||
sa: {
|
||||
intlMessagesId: 'learn.course.license.creativeCommons.terms.sa',
|
||||
icon: faCreativeCommonsSa,
|
||||
},
|
||||
zero: {
|
||||
intlMessagesId: 'learn.course.license.creativeCommons.terms.zero',
|
||||
icon: faCreativeCommonsZero,
|
||||
},
|
||||
};
|
||||
|
||||
function parseLicense(license) {
|
||||
if (!license) {
|
||||
// Default to All Rights Reserved if no license
|
||||
// is detected
|
||||
return ['all-rights-reserved', {}];
|
||||
}
|
||||
|
||||
// Search for a colon character denoting the end
|
||||
// of the license type and start of the options
|
||||
const colonIndex = license.indexOf(':');
|
||||
if (colonIndex === -1) {
|
||||
// no options, so the entire thing is the license type
|
||||
return [license, {}];
|
||||
}
|
||||
|
||||
// Split the license on the colon
|
||||
const licenseType = license.slice(0, colonIndex).trim();
|
||||
const optionStr = license.slice(colonIndex + 1).trim();
|
||||
|
||||
let options = {};
|
||||
let version = '';
|
||||
|
||||
// Set the defaultVersion to 4.0
|
||||
const defaultVersion = '4.0';
|
||||
optionStr.split(' ').forEach(option => {
|
||||
// Split the option into key and value
|
||||
// Default the value to `true` if no value
|
||||
let key = '';
|
||||
let value = '';
|
||||
if (option.indexOf('=') !== -1) {
|
||||
[key, value] = option.split('=');
|
||||
} else {
|
||||
key = option;
|
||||
value = true;
|
||||
}
|
||||
|
||||
// Check for version
|
||||
if (key === 'ver') {
|
||||
version = value;
|
||||
} else {
|
||||
// Set the option key to lowercase to make
|
||||
// it easier to query
|
||||
options[key.toLowerCase()] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// No options
|
||||
if (Object.keys(options).length === 0) {
|
||||
// If no other options are set for the
|
||||
// license, set version to 1.0
|
||||
version = '1.0';
|
||||
|
||||
// Set the `zero` option so the link
|
||||
// works correctly
|
||||
options = {
|
||||
zero: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Set the version to whatever was included,
|
||||
// using `defaultVersion` as a fallback if unset
|
||||
version = version || defaultVersion;
|
||||
|
||||
return [licenseType, options, version];
|
||||
}
|
||||
|
||||
function CourseLicense({
|
||||
license,
|
||||
intl,
|
||||
}) {
|
||||
const renderAllRightsReservedLicense = () => (
|
||||
<div>
|
||||
<FontAwesomeIcon aria-hidden="true" className="mr-1" icon={faCopyright} />
|
||||
<span className="license-text">
|
||||
{intl.formatMessage(messages['learn.course.license.allRightsReserved.text'])}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderCreativeCommonsLicense = (activeCreativeCommonsLicenseTags, version) => (
|
||||
<a
|
||||
className="text-decoration-none text-gray-700"
|
||||
rel="license noopener noreferrer"
|
||||
target="_blank"
|
||||
href={`https://creativecommons.org/licenses/${activeCreativeCommonsLicenseTags.join('-')}/${version}/`}
|
||||
>
|
||||
<span className="sr-only">
|
||||
{intl.formatMessage(messages['learn.course.license.creativeCommons.terms.preamble'])}
|
||||
</span>
|
||||
<FontAwesomeIcon aria-hidden="true" className="mr-1" icon={faCreativeCommons} />
|
||||
{activeCreativeCommonsLicenseTags.map(tag => (
|
||||
<span key={tag}>
|
||||
<span className="sr-only">
|
||||
{intl.formatMessage(messages[CreativeCommonsLicenseTags[tag].intlMessagesId])}
|
||||
</span>
|
||||
<FontAwesomeIcon aria-hidden="true" className="mr-1" icon={CreativeCommonsLicenseTags[tag].icon} />
|
||||
</span>
|
||||
))}
|
||||
<span className="license-text">
|
||||
{intl.formatMessage(messages['learn.course.license.creativeCommons.text'])}
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
|
||||
const [licenseType, licenseOptions, licenseVersion] = parseLicense(license);
|
||||
|
||||
return (
|
||||
<div className="course-license text-right small">
|
||||
{licenseType === 'all-rights-reserved' && renderAllRightsReservedLicense()}
|
||||
{licenseType === 'creative-commons' && renderCreativeCommonsLicense(
|
||||
Object.keys(licenseOptions),
|
||||
licenseVersion,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CourseLicense.propTypes = {
|
||||
license: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
CourseLicense.defaultProps = {
|
||||
license: 'all-rights-reserved',
|
||||
};
|
||||
|
||||
export default injectIntl(CourseLicense);
|
||||
1
src/courseware/course/course-license/index.js
Normal file
1
src/courseware/course/course-license/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './CourseLicense';
|
||||
47
src/courseware/course/course-license/messages.js
Normal file
47
src/courseware/course/course-license/messages.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'learn.course.license.allRightsReserved.text': {
|
||||
id: 'learn.course.license.allRightsReserved.text',
|
||||
defaultMessage: 'All Rights Reserved',
|
||||
description: 'License text shown when using All Rights Reserved license type.',
|
||||
},
|
||||
'learn.course.license.creativeCommons.terms.preamble': {
|
||||
id: 'learn.course.license.creativeCommons.terms.preamble',
|
||||
defaultMessage: 'Creative Commons licensed content, with terms as follows:',
|
||||
description: 'Screen reader only text preamble before reading specific Creative Commons license terms.',
|
||||
},
|
||||
'learn.course.license.creativeCommons.terms.by': {
|
||||
id: 'learn.course.license.creativeCommons.terms.by',
|
||||
defaultMessage: 'Attribution',
|
||||
description: 'Creative Commons license text for Attribution term.',
|
||||
},
|
||||
'learn.course.license.creativeCommons.terms.nc': {
|
||||
id: 'learn.course.license.creativeCommons.terms.nc',
|
||||
defaultMessage: 'Noncommercial',
|
||||
description: 'Creative Commons license text for Noncommercial term.',
|
||||
},
|
||||
'learn.course.license.creativeCommons.terms.nd': {
|
||||
id: 'learn.course.license.creativeCommons.terms.nd',
|
||||
defaultMessage: 'No Derivatives',
|
||||
description: 'Creative Commons license text for No Derivatives term.',
|
||||
},
|
||||
'learn.course.license.creativeCommons.terms.sa': {
|
||||
id: 'learn.course.license.creativeCommons.terms.sa',
|
||||
defaultMessage: 'Share Alike',
|
||||
description: 'Creative Commons license text for Share Alike term.',
|
||||
},
|
||||
// No text for `zero` license
|
||||
'learn.course.license.creativeCommons.terms.zero': {
|
||||
id: 'learn.course.license.creativeCommons.terms.zero',
|
||||
defaultMessage: 'No terms',
|
||||
description: 'Creative Commons license text for license with no terms.',
|
||||
},
|
||||
'learn.course.license.creativeCommons.text': {
|
||||
id: 'learn.course.license.creativeCommons.text',
|
||||
defaultMessage: 'Some Rights Reserved',
|
||||
description: 'License text shown when using all Creative Commons license types.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -11,6 +11,7 @@ import PageLoading from '../../../generic/PageLoading';
|
||||
import { UserMessagesContext, ALERT_TYPES } from '../../../generic/user-messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
import CourseLicense from '../course-license';
|
||||
import messages from './messages';
|
||||
import { SequenceNavigation, UnitNavigation } from './sequence-navigation';
|
||||
import SequenceContent from './SequenceContent';
|
||||
@@ -24,6 +25,7 @@ function Sequence({
|
||||
previousSequenceHandler,
|
||||
intl,
|
||||
}) {
|
||||
const course = useModel('courses', courseId);
|
||||
const sequence = useModel('sequences', sequenceId);
|
||||
const unit = useModel('units', unitId);
|
||||
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
|
||||
@@ -157,6 +159,9 @@ function Sequence({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sequence-footer px-4 py-1">
|
||||
<CourseLicense license={course.license || undefined} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ Factory.define('courseMetadata')
|
||||
},
|
||||
show_calculator: false,
|
||||
is_staff: false,
|
||||
license: 'all-rights-reserved',
|
||||
can_load_courseware: {
|
||||
has_access: true,
|
||||
user_fragment: null,
|
||||
|
||||
@@ -127,6 +127,7 @@ function normalizeMetadata(metadata) {
|
||||
isEnrolled: metadata.enrollment.is_active,
|
||||
canLoadCourseware: camelCaseObject(metadata.can_load_courseware),
|
||||
isStaff: metadata.is_staff,
|
||||
license: metadata.license,
|
||||
verifiedMode: camelCaseObject(metadata.verified_mode),
|
||||
tabs: normalizeTabUrls(metadata.id, camelCaseObject(metadata.tabs)),
|
||||
showCalculator: metadata.show_calculator,
|
||||
|
||||
@@ -262,6 +262,10 @@ $primary: #1176B2;
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-footer .course-license a:hover {
|
||||
color: theme-color('primary', 500) !important;
|
||||
}
|
||||
|
||||
.unit-container {
|
||||
padding: 0 $grid-gutter-width 2rem;
|
||||
max-width: 1024px;
|
||||
|
||||
Reference in New Issue
Block a user