From 6713d6c7e3de8ae6aeed0b8ab81bfa84ca8b58d9 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Fri, 27 Sep 2013 16:36:08 +0300 Subject: [PATCH 01/75] Work in progress. --- .../xmodule/js/src/video/01_initialize.js | 1 + .../xmodule/js/src/video/09_video_caption.js | 256 ++++++++++++------ 2 files changed, 168 insertions(+), 89 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js index 7f8057c025..d9f0addabf 100644 --- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js @@ -271,6 +271,7 @@ function (VideoPlayer) { // The parent element of the video, and the ID. this.el = $(element).find('.video'); + this.elVideoWrapper = this.el.find('.video-wrapper'); this.id = this.el.attr('id').replace(/video_/, ''); console.log( diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index b97717e97c..13f2fe0db0 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -34,46 +34,63 @@ function () { // function _makeFunctionsPublic(state) // - // Functions which will be accessible via 'state' object. When called, these functions will - // get the 'state' object as a context. + // Functions which will be accessible via 'state' object. When called, + // these functions will get the 'state' object as a context. function _makeFunctionsPublic(state) { - state.videoCaption.autoShowCaptions = _.bind(autoShowCaptions, state); - state.videoCaption.autoHideCaptions = _.bind(autoHideCaptions, state); - state.videoCaption.resize = _.bind(resize, state); - state.videoCaption.toggle = _.bind(toggle, state); - state.videoCaption.onMouseEnter = _.bind(onMouseEnter, state); - state.videoCaption.onMouseLeave = _.bind(onMouseLeave, state); - state.videoCaption.onMovement = _.bind(onMovement, state); - state.videoCaption.renderCaption = _.bind(renderCaption, state); - state.videoCaption.captionHeight = _.bind(captionHeight, state); - state.videoCaption.topSpacingHeight = _.bind(topSpacingHeight, state); - state.videoCaption.bottomSpacingHeight = _.bind(bottomSpacingHeight, state); - state.videoCaption.scrollCaption = _.bind(scrollCaption, state); - state.videoCaption.search = _.bind(search, state); - state.videoCaption.play = _.bind(play, state); - state.videoCaption.pause = _.bind(pause, state); - state.videoCaption.seekPlayer = _.bind(seekPlayer, state); - state.videoCaption.hideCaptions = _.bind(hideCaptions, state); - state.videoCaption.calculateOffset = _.bind(calculateOffset, state); - state.videoCaption.updatePlayTime = _.bind(updatePlayTime, state); - state.videoCaption.setSubtitlesHeight = _.bind(setSubtitlesHeight, state); + state.videoCaption.autoShowCaptions = _.bind( + autoShowCaptions, state + ); + state.videoCaption.autoHideCaptions = _.bind( + autoHideCaptions, state + ); + state.videoCaption.resize = _.bind(resize, state); + state.videoCaption.toggle = _.bind(toggle, state); + state.videoCaption.onMouseEnter = _.bind(onMouseEnter, state); + state.videoCaption.onMouseLeave = _.bind(onMouseLeave, state); + state.videoCaption.onMovement = _.bind(onMovement, state); + state.videoCaption.renderCaption = _.bind(renderCaption, state); + state.videoCaption.captionHeight = _.bind(captionHeight, state); + state.videoCaption.topSpacingHeight = _.bind( + topSpacingHeight, state + ); + state.videoCaption.bottomSpacingHeight = _.bind( + bottomSpacingHeight, state + ); + state.videoCaption.scrollCaption = _.bind(scrollCaption, state); + state.videoCaption.search = _.bind(search, state); + state.videoCaption.play = _.bind(play, state); + state.videoCaption.pause = _.bind(pause, state); + state.videoCaption.seekPlayer = _.bind(seekPlayer, state); + state.videoCaption.hideCaptions = _.bind(hideCaptions, state); + state.videoCaption.calculateOffset = _.bind( + calculateOffset, state + ); + state.videoCaption.updatePlayTime = _.bind(updatePlayTime, state); + state.videoCaption.setSubtitlesHeight = _.bind( + setSubtitlesHeight, state + ); - state.videoCaption.renderElements = _.bind(renderElements, state); - state.videoCaption.bindHandlers = _.bind(bindHandlers, state); - state.videoCaption.fetchCaption = _.bind(fetchCaption, state); - state.videoCaption.captionURL = _.bind(captionURL, state); - state.videoCaption.captionMouseOverOut = _.bind(captionMouseOverOut, state); - state.videoCaption.captionMouseDown = _.bind(captionMouseDown, state); - state.videoCaption.captionClick = _.bind(captionClick, state); - state.videoCaption.captionFocus = _.bind(captionFocus, state); - state.videoCaption.captionBlur = _.bind(captionBlur, state); - state.videoCaption.captionKeyDown = _.bind(captionKeyDown, state); + state.videoCaption.renderElements = _.bind(renderElements, state); + state.videoCaption.bindHandlers = _.bind(bindHandlers, state); + state.videoCaption.fetchCaption = _.bind(fetchCaption, state); + state.videoCaption.captionURL = _.bind(captionURL, state); + state.videoCaption.captionMouseOverOut = _.bind( + captionMouseOverOut, state + ); + state.videoCaption.captionMouseDown = _.bind( + captionMouseDown, state + ); + state.videoCaption.captionClick = _.bind(captionClick, state); + state.videoCaption.captionFocus = _.bind(captionFocus, state); + state.videoCaption.captionBlur = _.bind(captionBlur, state); + state.videoCaption.captionKeyDown = _.bind(captionKeyDown, state); } // *************************************************************** // Public functions start here. - // These are available via the 'state' object. Their context ('this' keyword) is the 'state' object. - // The magic private function that makes them available and sets up their context is makeFunctionsPublic(). + // These are available via the 'state' object. Their context ('this' + // keyword) is the 'state' object. The magic private function that makes + // them available and sets up their context is makeFunctionsPublic(). // *************************************************************** /** @@ -109,10 +126,13 @@ function () { // function bindHandlers() // - // Bind any necessary function callbacks to DOM events (click, mousemove, etc.). + // Bind any necessary function callbacks to DOM events (click, + // mousemove, etc.). function bindHandlers() { $(window).bind('resize', this.videoCaption.resize); - this.videoCaption.hideSubtitlesEl.on('click', this.videoCaption.toggle); + this.videoCaption.hideSubtitlesEl.on( + 'click', this.videoCaption.toggle + ); this.videoCaption.subtitlesEl .on( @@ -138,8 +158,12 @@ function () { // Moving slider on subtitles is not a mouse move, // but captions and controls should be showed. - this.videoCaption.subtitlesEl.on('scroll', this.videoCaption.autoShowCaptions); - this.videoCaption.subtitlesEl.on('scroll', this.videoControl.showControls); + this.videoCaption.subtitlesEl.on( + 'scroll', this.videoCaption.autoShowCaptions + ); + this.videoCaption.subtitlesEl.on( + 'scroll', this.videoControl.showControls + ); } } @@ -209,7 +233,8 @@ function () { } function captionURL() { - return '' + this.config.caption_asset_path + this.youtubeId('1.0') + '.srt.sjson'; + return '' + this.config.caption_asset_path + + this.youtubeId('1.0') + '.srt.sjson'; } function autoShowCaptions(event) { @@ -224,13 +249,17 @@ function () { this.videoCaption.subtitlesEl.show(); this.captionState = 'visible'; } else if (this.captionState === 'hiding') { - this.videoCaption.subtitlesEl.stop(true, false).css('opacity', 1).show(); + this.videoCaption.subtitlesEl + .stop(true, false).css('opacity', 1).show(); this.captionState = 'visible'; } else if (this.captionState === 'visible') { clearTimeout(this.captionHideTimeout); } - this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout); + this.captionHideTimeout = setTimeout( + this.videoCaption.autoHideCaptions, + this.videoCaption.fadeOutTimeout + ); this.captionsShowLock = false; } @@ -249,15 +278,20 @@ function () { _this = this; - this.videoCaption.subtitlesEl.fadeOut(this.videoCaption.fadeOutTimeout, function () { + this.videoCaption.subtitlesEl.fadeOut( + this.videoCaption.fadeOutTimeout, + function () { + _this.captionState = 'invisible'; }); } function resize() { this.videoCaption.subtitlesEl - .find('.spacing:first').height(this.videoCaption.topSpacingHeight()) - .find('.spacing:last').height(this.videoCaption.bottomSpacingHeight()); + .find('.spacing:first') + .height(this.videoCaption.topSpacingHeight()) + .find('.spacing:last') + .height(this.videoCaption.bottomSpacingHeight()); this.videoCaption.scrollCaption(); @@ -269,7 +303,10 @@ function () { clearTimeout(this.videoCaption.frozen); } - this.videoCaption.frozen = setTimeout(this.videoCaption.onMouseLeave, 10000); + this.videoCaption.frozen = setTimeout( + this.videoCaption.onMouseLeave, + 10000 + ); } function onMouseLeave() { @@ -292,8 +329,9 @@ function () { var container = $('
    '), _this = this; - this.el.find('.video-wrapper').after(this.videoCaption.subtitlesEl); - this.el.find('.video-controls .secondary-controls').append(this.videoCaption.hideSubtitlesEl); + this.elVideoWrapper.after(this.videoCaption.subtitlesEl); + this.el.find('.video-controls .secondary-controls') + .append(this.videoCaption.hideSubtitlesEl); this.videoCaption.setSubtitlesHeight(); @@ -301,7 +339,10 @@ function () { this.videoCaption.fadeOutTimeout = this.config.fadeOutTimeout; this.videoCaption.subtitlesEl.addClass('html5'); - this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout); + this.captionHideTimeout = setTimeout( + this.videoCaption.autoHideCaptions, + this.videoCaption.fadeOutTimeout + ); } this.videoCaption.hideCaptions(this.hide_captions); @@ -337,23 +378,30 @@ function () { // Enables or disables automatic scrolling of the captions when the // video is playing. This feature has to be disabled when tabbing // through them as it interferes with that action. Initially, have this - // flag enabled as we assume mouse use. Then, if the first caption + // flag enabled as we assume mouse use. Then, if the first caption // (through forward tabbing) or the last caption (through backwards // tabbing) gets the focus, disable that feature. Renable it if tabbing - // then cycles out of the the captions. + // then cycles out of the the captions. this.videoCaption.autoScrolling = true; // Keeps track of where the focus is situated in the array of captions. // Used to implement the automatic scrolling behavior and decide if the - // outline around a caption has to be hidden or shown on a mouseenter or - // mouseleave. Initially, no caption has the focus, set the index to -1. + // outline around a caption has to be hidden or shown on a mouseenter + // or mouseleave. Initially, no caption has the focus, set the + // index to -1. this.videoCaption.currentCaptionIndex = -1; - // Used to track if the focus is coming from a click or tabbing. This - // has to be known to decide if, when a caption gets the focus, an + // Used to track if the focus is coming from a click or tabbing. This + // has to be known to decide if, when a caption gets the focus, an // outline has to be drawn (tabbing) or not (mouse click). this.videoCaption.isMouseFocus = false; - this.videoCaption.subtitlesEl.prepend($('
  1. ').height(this.videoCaption.topSpacingHeight())); - this.videoCaption.subtitlesEl.append($('
  2. ').height(this.videoCaption.bottomSpacingHeight())); + this.videoCaption.subtitlesEl.prepend( + $('
  3. ') + .height(this.videoCaption.topSpacingHeight()) + ); + this.videoCaption.subtitlesEl.append( + $('
  4. ') + .height(this.videoCaption.bottomSpacingHeight()) + ); this.videoCaption.rendered = true; } @@ -362,7 +410,7 @@ function () { // On mouseOut, show the outline of a caption that has been tabbed to. function captionMouseOverOut(event) { var caption = $(event.target), - captionIndex = parseInt(caption.attr('data-index'), 10); + captionIndex = parseInt(caption.attr('data-index'), 10); if (captionIndex === this.videoCaption.currentCaptionIndex) { if (event.type === 'mouseover') { caption.removeClass('focused'); @@ -370,7 +418,7 @@ function () { else { // mouseout caption.addClass('focused'); } - } + } } function captionMouseDown(event) { @@ -390,35 +438,38 @@ function () { captionIndex = parseInt(caption.attr('data-index'), 10); // If the focus comes from a mouse click, hide the outline, turn on // automatic scrolling and set currentCaptionIndex to point outside of - // caption list (ie -1) to disable mouseenter, mouseleave behavior. + // caption list (ie -1) to disable mouseenter, mouseleave behavior. if (this.videoCaption.isMouseFocus) { this.videoCaption.autoScrolling = true; caption.removeClass('focused'); this.videoCaption.currentCaptionIndex = -1; } - // If the focus comes from tabbing, show the outline and turn off + // If the focus comes from tabbing, show the outline and turn off // automatic scrolling. else { this.videoCaption.currentCaptionIndex = captionIndex; caption.addClass('focused'); // The second and second to last elements turn automatic scrolling - // off again as it may have been enabled in captionBlur. - if (captionIndex <= 1 || captionIndex >= this.videoCaption.captions.length-2) { + // off again as it may have been enabled in captionBlur. + if ( + captionIndex <= 1 || + captionIndex >= this.videoCaption.captions.length - 2 + ) { this.videoCaption.autoScrolling = false; } } } function captionBlur(event) { - var caption = $(event.target), + var caption = $(event.target), captionIndex = parseInt(caption.attr('data-index'), 10); caption.removeClass('focused'); - // If we are on first or last index, we have to turn automatic scroll on - // again when losing focus. There is no way to know in what direction we - // are tabbing. So we could be on the first element and tabbing back out - // of the captions or on the last element and tabbing forward out of the - // captions. - if (captionIndex === 0 || + // If we are on first or last index, we have to turn automatic scroll + // on again when losing focus. There is no way to know in what + // direction we are tabbing. So we could be on the first element and + // tabbing back out of the captions or on the last element and tabbing + // forward out of the captions. + if (captionIndex === 0 || captionIndex === this.videoCaption.captions.length-1) { this.videoCaption.autoScrolling = true; } @@ -434,9 +485,13 @@ function () { function scrollCaption() { var el = this.videoCaption.subtitlesEl.find('.current:first'); - // Automatic scrolling gets disabled if one of the captions has received - // focus through tabbing. - if (!this.videoCaption.frozen && el.length && this.videoCaption.autoScrolling) { + // Automatic scrolling gets disabled if one of the captions has + // received focus through tabbing. + if ( + !this.videoCaption.frozen && + el.length && + this.videoCaption.autoScrolling + ) { this.videoCaption.subtitlesEl.scrollTo( el, { @@ -565,11 +620,15 @@ function () { } function topSpacingHeight() { - return this.videoCaption.calculateOffset(this.videoCaption.subtitlesEl.find('li:not(.spacing):first')); + return this.videoCaption.calculateOffset( + this.videoCaption.subtitlesEl.find('li:not(.spacing):first') + ); } function bottomSpacingHeight() { - return this.videoCaption.calculateOffset(this.videoCaption.subtitlesEl.find('li:not(.spacing):last')); + return this.videoCaption.calculateOffset( + this.videoCaption.subtitlesEl.find('li:not(.spacing):last') + ); } function toggle(event) { @@ -588,14 +647,20 @@ function () { if (hide_captions) { type = 'hide_transcript'; this.captionsHidden = true; - this.videoCaption.hideSubtitlesEl.attr('title', gettext('Turn on captions')); - this.videoCaption.hideSubtitlesEl.text(gettext('Turn on captions')); + this.videoCaption.hideSubtitlesEl.attr( + 'title', gettext('Turn on captions') + ); + this.videoCaption.hideSubtitlesEl + .text(gettext('Turn on captions')); this.el.addClass('closed'); } else { type = 'show_transcript'; this.captionsHidden = false; - this.videoCaption.hideSubtitlesEl.attr('title', gettext('Turn off captions')); - this.videoCaption.hideSubtitlesEl.text(gettext('Turn off captions')); + this.videoCaption.hideSubtitlesEl.attr( + 'title', gettext('Turn off captions') + ); + this.videoCaption.hideSubtitlesEl + .text(gettext('Turn off captions')); this.el.removeClass('closed'); this.videoCaption.scrollCaption(); } @@ -615,12 +680,19 @@ function () { } function captionHeight() { + var paddingTop; + if (this.isFullScreen) { - return $(window).height() - this.el.find('.video-controls').height() - - 0.5 * this.videoControl.sliderEl.height() - - 2 * parseInt(this.videoCaption.subtitlesEl.css('padding-top'), 10); + paddingTop = parseInt( + this.videoCaption.subtitlesEl.css('padding-top'), 10 + ); + + return $(window).height() - + this.videoControl.el.height() - + 0.5 * this.videoControl.sliderEl.height() - + 2 * paddingTop; } else { - return this.el.find('.video-wrapper').height(); + return this.elVideoWrapper.height(); } } @@ -629,13 +701,19 @@ function () { if (this.videoType === 'html5'){ // on page load captionHidden = undefined if ( - (this.captionsHidden === undefined && this.hide_captions === true ) || - (this.captionsHidden === true) ) { - // In case of html5 autoshowing subtitles, - // we ajdust height of subs, by height of scrollbar - height = this.videoControl.el.height() + 0.5 * this.videoControl.sliderEl.height(); - // height of videoControl does not contain height of slider. - // (css is set to absolute, to avoid yanking when slider autochanges its height) + ( + this.captionsHidden === undefined && + this.hide_captions === true + ) || + (this.captionsHidden === true) + ) { + // In case of html5 autoshowing subtitles, we adjust height of + // subs, by height of scrollbar. + height = this.videoControl.el.height() + + 0.5 * this.videoControl.sliderEl.height(); + // Height of videoControl does not contain height of slider. + // css is set to absolute, to avoid yanking when slider + // autochanges its height. } } this.videoCaption.subtitlesEl.css({ From 9f871e53c85c00370f4752bb31c23fdf9dee3d17 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Mon, 30 Sep 2013 14:13:34 +0300 Subject: [PATCH 02/75] Updated code fomat in test Jasmine. --- .../js/spec/video/video_caption_spec.js | 1451 +++++++++-------- 1 file changed, 788 insertions(+), 663 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js index 0f729da62d..061576efd2 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js @@ -1,673 +1,798 @@ -(function() { - describe('VideoCaption', function() { - var state, videoPlayer, videoCaption, videoSpeedControl, oldOTBD; +(function () { + describe('VideoCaption', function () { + var state, videoPlayer, videoCaption, videoSpeedControl, oldOTBD; - function initialize() { - loadFixtures('video_all.html'); - state = new Video('#example'); - videoPlayer = state.videoPlayer; - videoCaption = state.videoCaption; - videoSpeedControl = state.videoSpeedControl; - videoControl = state.videoControl; - } + function initialize() { + loadFixtures('video_all.html'); + state = new Video('#example'); + videoPlayer = state.videoPlayer; + videoCaption = state.videoCaption; + videoSpeedControl = state.videoSpeedControl; + videoControl = state.videoControl; + } - beforeEach(function() { - oldOTBD = window.onTouchBasedDevice; - window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false); - initialize(); - }); - - afterEach(function() { - YT.Player = void 0; - $.fn.scrollTo.reset(); - $('.subtitles').remove(); - $('source').remove(); - window.onTouchBasedDevice = oldOTBD; - }); - - describe('constructor', function() { - describe('always', function() { - beforeEach(function() { - spyOn($, 'ajaxWithPrefix').andCallThrough(); - initialize(); - }); - - it('create the caption element', function() { - expect($('.video')).toContain('ol.subtitles'); - }); - - it('add caption control to video player', function() { - expect($('.video')).toContain('a.hide-subtitles'); - }); - - it('fetch the caption', function() { - waitsFor(function () { - if (videoCaption.loaded === true) { - return true; - } - - return false; - }, 'Expect captions to be loaded.', 1000); - - runs(function () { - expect($.ajaxWithPrefix).toHaveBeenCalledWith({ - url: videoCaption.captionURL(), - notifyOnError: false, - success: jasmine.any(Function), - error: jasmine.any(Function), - }); - }); - }); - - it('bind window resize event', function() { - expect($(window)).toHandleWith('resize', videoCaption.resize); - }); - - it('bind the hide caption button', function() { - expect($('.hide-subtitles')).toHandleWith('click', videoCaption.toggle); - }); - - it('bind the mouse movement', function() { - expect($('.subtitles')).toHandleWith('mouseover', videoCaption.onMouseEnter); - expect($('.subtitles')).toHandleWith('mouseout', videoCaption.onMouseLeave); - expect($('.subtitles')).toHandleWith('mousemove', videoCaption.onMovement); - expect($('.subtitles')).toHandleWith('mousewheel', videoCaption.onMovement); - expect($('.subtitles')).toHandleWith('DOMMouseScroll', videoCaption.onMovement); - }); - - it('bind the scroll', function() { - expect($('.subtitles')).toHandleWith('scroll', videoCaption.autoShowCaptions); - expect($('.subtitles')).toHandleWith('scroll', videoControl.showControls); - }); - }); - - describe('when on a non touch-based device', function() { - beforeEach(function() { - initialize(); - }); - - it('render the caption', function() { - var captionsData; - - captionsData = jasmine.stubbedCaption; - $('.subtitles li[data-index]').each(function(index, link) { - expect($(link)).toHaveData('index', index); - expect($(link)).toHaveData('start', captionsData.start[index]); - expect($(link)).toHaveAttr('tabindex', 0); - expect($(link)).toHaveText(captionsData.text[index]); - }); - }); - - it('add a padding element to caption', function() { - expect($('.subtitles li:first').hasClass('spacing')).toBe(true); - expect($('.subtitles li:last').hasClass('spacing')).toBe(true); - }); - - it('bind all the caption link', function() { - $('.subtitles li[data-index]').each(function(index, link) { - expect($(link)).toHandleWith('mouseover', videoCaption.captionMouseOverOut); - expect($(link)).toHandleWith('mouseout', videoCaption.captionMouseOverOut); - expect($(link)).toHandleWith('mousedown', videoCaption.captionMouseDown); - expect($(link)).toHandleWith('click', videoCaption.captionClick); - expect($(link)).toHandleWith('focus', videoCaption.captionFocus); - expect($(link)).toHandleWith('blur', videoCaption.captionBlur); - expect($(link)).toHandleWith('keydown', videoCaption.captionKeyDown); - }); - }); - - it('set rendered to true', function() { - expect(videoCaption.rendered).toBeTruthy(); - }); - }); - - describe('when on a touch-based device', function() { - beforeEach(function() { - window.onTouchBasedDevice.andReturn(true); - initialize(); - }); - - it('show explaination message', function() { - expect($('.subtitles li')).toHaveHtml("Caption will be displayed when you start playing the video."); - }); - - it('does not set rendered to true', function() { - expect(videoCaption.rendered).toBeFalsy(); - }); - }); - - describe('when no captions file was specified', function () { - beforeEach(function () { - loadFixtures('video_all.html'); - - // Unspecify the captions file. - $('#example').find('#video_id').data('sub', ''); - - state = new Video('#example'); - videoCaption = state.videoCaption; - }); - - it('captions panel is not shown', function () { - expect(videoCaption.hideSubtitlesEl).toBeHidden(); - }); - }); - }); - - describe('mouse movement', function() { - // We will store default window.setTimeout() function here. - var oldSetTimeout = null; - - beforeEach(function() { - // Store original window.setTimeout() function. If we do not do this, then - // all other tests that rely on code which uses window.setTimeout() - // function might (and probably will) fail. - oldSetTimeout = window.setTimeout; - // Redefine window.setTimeout() function as a spy. - window.setTimeout = jasmine.createSpy().andCallFake(function(callback, timeout) { return 5; }) - window.setTimeout.andReturn(100); - spyOn(window, 'clearTimeout'); - }); - - afterEach(function () { - // Reset the default window.setTimeout() function. If we do not do this, - // then all other tests that rely on code which uses window.setTimeout() - // function might (and probably will) fail. - window.setTimeout = oldSetTimeout; - }); - - describe('when cursor is outside of the caption box', function() { - beforeEach(function() { - $(window).trigger(jQuery.Event('mousemove')); - }); - - it('does not set freezing timeout', function() { - expect(videoCaption.frozen).toBeFalsy(); - }); - }); - - describe('when cursor is in the caption box', function() { - beforeEach(function() { - $('.subtitles').trigger(jQuery.Event('mouseenter')); - }); - - it('set the freezing timeout', function() { - expect(videoCaption.frozen).toEqual(100); - }); - - describe('when the cursor is moving', function() { - beforeEach(function() { - $('.subtitles').trigger(jQuery.Event('mousemove')); - }); - - it('reset the freezing timeout', function() { - expect(window.clearTimeout).toHaveBeenCalledWith(100); - }); - }); - - describe('when the mouse is scrolling', function() { - beforeEach(function() { - $('.subtitles').trigger(jQuery.Event('mousewheel')); - }); - - it('reset the freezing timeout', function() { - expect(window.clearTimeout).toHaveBeenCalledWith(100); - }); - }); - }); - - describe('when cursor is moving out of the caption box', function() { - beforeEach(function() { - videoCaption.frozen = 100; - $.fn.scrollTo.reset(); - }); - - describe('always', function() { - beforeEach(function() { - $('.subtitles').trigger(jQuery.Event('mouseout')); - }); - - it('reset the freezing timeout', function() { - expect(window.clearTimeout).toHaveBeenCalledWith(100); - }); - - it('unfreeze the caption', function() { - expect(videoCaption.frozen).toBeNull(); - }); - }); - - describe('when the player is playing', function() { - beforeEach(function() { - videoCaption.playing = true; - $('.subtitles li[data-index]:first').addClass('current'); - $('.subtitles').trigger(jQuery.Event('mouseout')); - }); - - it('scroll the caption', function() { - expect($.fn.scrollTo).toHaveBeenCalled(); - }); - }); - - describe('when the player is not playing', function() { - beforeEach(function() { - videoCaption.playing = false; - $('.subtitles').trigger(jQuery.Event('mouseout')); - }); - - it('does not scroll the caption', function() { - expect($.fn.scrollTo).not.toHaveBeenCalled(); - }); - }); - }); - }); - - describe('search', function() { - it('return a correct caption index', function() { - expect(videoCaption.search(0)).toEqual(-1); - expect(videoCaption.search(3120)).toEqual(1); - expect(videoCaption.search(6270)).toEqual(2); - expect(videoCaption.search(8490)).toEqual(2); - expect(videoCaption.search(21620)).toEqual(4); - expect(videoCaption.search(24920)).toEqual(5); - }); - }); - - describe('play', function() { - describe('when the caption was not rendered', function() { - beforeEach(function() { - window.onTouchBasedDevice.andReturn(true); - initialize(); - videoCaption.play(); - }); - - it('render the caption', function() { - var captionsData; - - captionsData = jasmine.stubbedCaption; - $('.subtitles li[data-index]').each(function(index, link) { - expect($(link)).toHaveData('index', index); - expect($(link)).toHaveData('start', captionsData.start[index]); - expect($(link)).toHaveAttr('tabindex', 0); - expect($(link)).toHaveText(captionsData.text[index]); - }); - }); - - it('add a padding element to caption', function() { - expect($('.subtitles li:first')).toBe('.spacing'); - expect($('.subtitles li:last')).toBe('.spacing'); - }); - - it('bind all the caption link', function() { - $('.subtitles li[data-index]').each(function(index, link) { - expect($(link)).toHandleWith('mouseover', videoCaption.captionMouseOverOut); - expect($(link)).toHandleWith('mouseout', videoCaption.captionMouseOverOut); - expect($(link)).toHandleWith('mousedown', videoCaption.captionMouseDown); - expect($(link)).toHandleWith('click', videoCaption.captionClick); - expect($(link)).toHandleWith('focus', videoCaption.captionFocus); - expect($(link)).toHandleWith('blur', videoCaption.captionBlur); - expect($(link)).toHandleWith('keydown', videoCaption.captionKeyDown); - }); - }); - - it('set rendered to true', function() { - expect(videoCaption.rendered).toBeTruthy(); - }); - - it('set playing to true', function() { - expect(videoCaption.playing).toBeTruthy(); - }); - }); - }); - - describe('pause', function() { - beforeEach(function() { - videoCaption.playing = true; - videoCaption.pause(); - }); - - it('set playing to false', function() { - expect(videoCaption.playing).toBeFalsy(); - }); - }); - - describe('updatePlayTime', function() { - describe('when the video speed is 1.0x', function() { - beforeEach(function() { - videoSpeedControl.currentSpeed = '1.0'; - videoCaption.updatePlayTime(25.000); - }); - - it('search the caption based on time', function() { - expect(videoCaption.currentIndex).toEqual(5); - }); - }); - - describe('when the video speed is not 1.0x', function() { - beforeEach(function() { - videoSpeedControl.currentSpeed = '0.75'; - videoCaption.updatePlayTime(25.000); - }); - - it('search the caption based on 1.0x speed', function() { - expect(videoCaption.currentIndex).toEqual(5); - }); - }); - - describe('when the index is not the same', function() { - beforeEach(function() { - videoCaption.currentIndex = 1; - $('.subtitles li[data-index=5]').addClass('current'); - videoCaption.updatePlayTime(25.000); - }); - - it('deactivate the previous caption', function() { - expect($('.subtitles li[data-index=1]')).not.toHaveClass('current'); - }); - - it('activate new caption', function() { - expect($('.subtitles li[data-index=5]')).toHaveClass('current'); - }); - - it('save new index', function() { - expect(videoCaption.currentIndex).toEqual(5); - }); - - it('scroll caption to new position', function() { - expect($.fn.scrollTo).toHaveBeenCalled(); - }); - }); - - describe('when the index is the same', function() { - beforeEach(function() { - videoCaption.currentIndex = 1; - $('.subtitles li[data-index=3]').addClass('current'); - videoCaption.updatePlayTime(15.000); - }); - - it('does not change current subtitle', function() { - expect($('.subtitles li[data-index=3]')).toHaveClass('current'); - }); - }); - }); - - describe('resize', function() { - beforeEach(function() { - initialize(); - $('.subtitles li[data-index=1]').addClass('current'); - videoCaption.resize(); - }); - - describe('set the height of caption container', function(){ - // Temporarily disabled due to intermittent failures - // with error "Expected 745 to be close to 805, 2." in Firefox - xit('when CC button is enabled', function() { - var realHeight = parseInt($('.subtitles').css('maxHeight'), 10), - shouldBeHeight = $('.video-wrapper').height(); - - // Because of some problems with rounding on different enviroments: - // Linux * Mac * FF * Chrome - expect(realHeight).toBeCloseTo(shouldBeHeight, 2); - }); - - it('when CC button is disabled ', function() { - var realHeight, videoWrapperHeight, progressSliderHeight, - controlHeight, shouldBeHeight; - - state.captionsHidden = true; - videoCaption.setSubtitlesHeight(); - - realHeight = parseInt($('.subtitles').css('maxHeight'), 10); - videoWrapperHeight = $('.video-wrapper').height(); - progressSliderHeight = videoControl.sliderEl.height(); - controlHeight = videoControl.el.height(); - shouldBeHeight = videoWrapperHeight - - 0.5 * progressSliderHeight - - controlHeight; - - expect(realHeight).toBe(shouldBeHeight); - }); - }); - - it('set the height of caption spacing', function() { - var firstSpacing, lastSpacing; - firstSpacing = Math.abs(parseInt($('.subtitles .spacing:first').css('height'), 10)); - lastSpacing = Math.abs(parseInt($('.subtitles .spacing:last').css('height'), 10)); - expect(firstSpacing - videoCaption.topSpacingHeight()).toBeLessThan(1); - expect(lastSpacing - videoCaption.bottomSpacingHeight()).toBeLessThan(1); - }); - - it('scroll caption to new position', function() { - expect($.fn.scrollTo).toHaveBeenCalled(); - }); - }); - - describe('scrollCaption', function() { - beforeEach(function() { - initialize(); - }); - - describe('when frozen', function() { - beforeEach(function() { - videoCaption.frozen = true; - $('.subtitles li[data-index=1]').addClass('current'); - videoCaption.scrollCaption(); - }); - - it('does not scroll the caption', function() { - expect($.fn.scrollTo).not.toHaveBeenCalled(); - }); - }); - - describe('when not frozen', function() { - beforeEach(function() { - videoCaption.frozen = false; - }); - - describe('when there is no current caption', function() { - beforeEach(function() { - videoCaption.scrollCaption(); - }); - - it('does not scroll the caption', function() { - expect($.fn.scrollTo).not.toHaveBeenCalled(); - }); - }); - - describe('when there is a current caption', function() { - beforeEach(function() { - $('.subtitles li[data-index=1]').addClass('current'); - videoCaption.scrollCaption(); - }); - - it('scroll to current caption', function() { - expect($.fn.scrollTo).toHaveBeenCalled(); - }); - }); - }); - }); - - describe('seekPlayer', function() { - describe('when the video speed is 1.0x', function() { - beforeEach(function() { - videoSpeedControl.currentSpeed = '1.0'; - $('.subtitles li[data-start="14910"]').trigger('click'); - }); - - // Temporarily disabled due to intermittent failures - // Fails with error: "InvalidStateError: An attempt was made to - // use an object that is not, or is no longer, usable - // Expected 0 to equal 14.91." - // on Firefox - xit('trigger seek event with the correct time', function() { - expect(videoPlayer.currentTime).toEqual(14.91); - }); - }); - - describe('when the video speed is not 1.0x', function() { - beforeEach(function() { - initialize(); - videoSpeedControl.currentSpeed = '0.75'; - $('.subtitles li[data-start="14910"]').trigger('click'); - }); - - it('trigger seek event with the correct time', function() { - expect(videoPlayer.currentTime).toEqual(14.91); - }); - }); - - describe('when the player type is Flash at speed 0.75x', function () { beforeEach(function () { + oldOTBD = window.onTouchBasedDevice; + window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice') + .andReturn(false); initialize(); - videoSpeedControl.currentSpeed = '0.75'; - state.currentPlayerMode = 'flash'; - $('.subtitles li[data-start="14910"]').trigger('click'); }); - it('trigger seek event with the correct time', function () { - expect(videoPlayer.currentTime).toEqual(15); + afterEach(function () { + YT.Player = undefined; + $.fn.scrollTo.reset(); + $('.subtitles').remove(); + $('source').remove(); + window.onTouchBasedDevice = oldOTBD; + }); + + describe('constructor', function () { + describe('always', function () { + beforeEach(function () { + spyOn($, 'ajaxWithPrefix').andCallThrough(); + initialize(); + }); + + it('create the caption element', function () { + expect($('.video')).toContain('ol.subtitles'); + }); + + it('add caption control to video player', function () { + expect($('.video')).toContain('a.hide-subtitles'); + }); + + it('fetch the caption', function () { + waitsFor(function () { + if (videoCaption.loaded === true) { + return true; + } + + return false; + }, 'Expect captions to be loaded.', 1000); + + runs(function () { + expect($.ajaxWithPrefix).toHaveBeenCalledWith({ + url: videoCaption.captionURL(), + notifyOnError: false, + success: jasmine.any(Function), + error: jasmine.any(Function) + }); + }); + }); + + it('bind window resize event', function () { + expect($(window)).toHandleWith( + 'resize', videoCaption.resize + ); + }); + + it('bind the hide caption button', function () { + expect($('.hide-subtitles')).toHandleWith( + 'click', videoCaption.toggle + ); + }); + + it('bind the mouse movement', function () { + expect($('.subtitles')).toHandleWith( + 'mouseover', videoCaption.onMouseEnter + ); + expect($('.subtitles')).toHandleWith( + 'mouseout', videoCaption.onMouseLeave + ); + expect($('.subtitles')).toHandleWith( + 'mousemove', videoCaption.onMovement + ); + expect($('.subtitles')).toHandleWith( + 'mousewheel', videoCaption.onMovement + ); + expect($('.subtitles')).toHandleWith( + 'DOMMouseScroll', videoCaption.onMovement + ); + }); + + it('bind the scroll', function () { + expect($('.subtitles')) + .toHandleWith('scroll', videoCaption.autoShowCaptions); + expect($('.subtitles')) + .toHandleWith('scroll', videoControl.showControls); + }); + }); + + describe('when on a non touch-based device', function () { + beforeEach(function () { + initialize(); + }); + + it('render the caption', function () { + var captionsData; + + captionsData = jasmine.stubbedCaption; + $('.subtitles li[data-index]').each( + function (index, link) { + + expect($(link)).toHaveData('index', index); + expect($(link)).toHaveData( + 'start', captionsData.start[index] + ); + expect($(link)).toHaveAttr('tabindex', 0); + expect($(link)).toHaveText(captionsData.text[index]); + }); + }); + + it('add a padding element to caption', function () { + expect($('.subtitles li:first').hasClass('spacing')) + .toBe(true); + expect($('.subtitles li:last').hasClass('spacing')) + .toBe(true); + }); + + it('bind all the caption link', function () { + $('.subtitles li[data-index]').each( + function (index, link) { + + expect($(link)).toHandleWith( + 'mouseover', videoCaption.captionMouseOverOut + ); + expect($(link)).toHandleWith( + 'mouseout', videoCaption.captionMouseOverOut + ); + expect($(link)).toHandleWith( + 'mousedown', videoCaption.captionMouseDown + ); + expect($(link)).toHandleWith( + 'click', videoCaption.captionClick + ); + expect($(link)).toHandleWith( + 'focus', videoCaption.captionFocus + ); + expect($(link)).toHandleWith( + 'blur', videoCaption.captionBlur + ); + expect($(link)).toHandleWith( + 'keydown', videoCaption.captionKeyDown + ); + }); + }); + + it('set rendered to true', function () { + expect(videoCaption.rendered).toBeTruthy(); + }); + }); + + describe('when on a touch-based device', function () { + beforeEach(function () { + window.onTouchBasedDevice.andReturn(true); + initialize(); + }); + + it('show explaination message', function () { + expect($('.subtitles li')).toHaveHtml( + 'Caption will be displayed when you start playing ' + + 'the video.' + ); + }); + + it('does not set rendered to true', function () { + expect(videoCaption.rendered).toBeFalsy(); + }); + }); + + describe('when no captions file was specified', function () { + beforeEach(function () { + loadFixtures('video_all.html'); + + // Unspecify the captions file. + $('#example').find('#video_id').data('sub', ''); + + state = new Video('#example'); + videoCaption = state.videoCaption; + }); + + it('captions panel is not shown', function () { + expect(videoCaption.hideSubtitlesEl).toBeHidden(); + }); + }); + }); + + describe('mouse movement', function () { + // We will store default window.setTimeout() function here. + var oldSetTimeout = null; + + beforeEach(function () { + // Store original window.setTimeout() function. If we do not do + // this, then all other tests that rely on code which uses + // window.setTimeout() function might (and probably will) fail. + oldSetTimeout = window.setTimeout; + // Redefine window.setTimeout() function as a spy. + window.setTimeout = jasmine.createSpy().andCallFake( + function (callback, timeout) { + return 5; + } + ); + window.setTimeout.andReturn(100); + spyOn(window, 'clearTimeout'); + }); + + afterEach(function () { + // Reset the default window.setTimeout() function. If we do not + // do this, then all other tests that rely on code which uses + // window.setTimeout() function might (and probably will) fail. + window.setTimeout = oldSetTimeout; + }); + + describe('when cursor is outside of the caption box', function () { + beforeEach(function () { + $(window).trigger(jQuery.Event('mousemove')); + }); + + it('does not set freezing timeout', function () { + expect(videoCaption.frozen).toBeFalsy(); + }); + }); + + describe('when cursor is in the caption box', function () { + beforeEach(function () { + $('.subtitles').trigger(jQuery.Event('mouseenter')); + }); + + it('set the freezing timeout', function () { + expect(videoCaption.frozen).toEqual(100); + }); + + describe('when the cursor is moving', function () { + beforeEach(function () { + $('.subtitles').trigger(jQuery.Event('mousemove')); + }); + + it('reset the freezing timeout', function () { + expect(window.clearTimeout).toHaveBeenCalledWith(100); + }); + }); + + describe('when the mouse is scrolling', function () { + beforeEach(function () { + $('.subtitles').trigger(jQuery.Event('mousewheel')); + }); + + it('reset the freezing timeout', function () { + expect(window.clearTimeout).toHaveBeenCalledWith(100); + }); + }); + }); + + describe( + 'when cursor is moving out of the caption box', + function () { + + beforeEach(function () { + videoCaption.frozen = 100; + $.fn.scrollTo.reset(); + }); + + describe('always', function () { + beforeEach(function () { + $('.subtitles').trigger(jQuery.Event('mouseout')); + }); + + it('reset the freezing timeout', function () { + expect(window.clearTimeout).toHaveBeenCalledWith(100); + }); + + it('unfreeze the caption', function () { + expect(videoCaption.frozen).toBeNull(); + }); + }); + + describe('when the player is playing', function () { + beforeEach(function () { + videoCaption.playing = true; + $('.subtitles li[data-index]:first') + .addClass('current'); + $('.subtitles').trigger(jQuery.Event('mouseout')); + }); + + it('scroll the caption', function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + + describe('when the player is not playing', function () { + beforeEach(function () { + videoCaption.playing = false; + $('.subtitles').trigger(jQuery.Event('mouseout')); + }); + + it('does not scroll the caption', function () { + expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('search', function () { + it('return a correct caption index', function () { + expect(videoCaption.search(0)).toEqual(-1); + expect(videoCaption.search(3120)).toEqual(1); + expect(videoCaption.search(6270)).toEqual(2); + expect(videoCaption.search(8490)).toEqual(2); + expect(videoCaption.search(21620)).toEqual(4); + expect(videoCaption.search(24920)).toEqual(5); + }); + }); + + describe('play', function () { + describe('when the caption was not rendered', function () { + beforeEach(function () { + window.onTouchBasedDevice.andReturn(true); + initialize(); + videoCaption.play(); + }); + + it('render the caption', function () { + var captionsData; + + captionsData = jasmine.stubbedCaption; + $('.subtitles li[data-index]').each( + function (index, link) { + + expect($(link)).toHaveData('index', index); + expect($(link)).toHaveData( + 'start', captionsData.start[index] + ); + expect($(link)).toHaveAttr('tabindex', 0); + expect($(link)).toHaveText(captionsData.text[index]); + }); + }); + + it('add a padding element to caption', function () { + expect($('.subtitles li:first')).toBe('.spacing'); + expect($('.subtitles li:last')).toBe('.spacing'); + }); + + it('bind all the caption link', function () { + $('.subtitles li[data-index]').each( + function (index, link) { + + expect($(link)).toHandleWith( + 'mouseover', videoCaption.captionMouseOverOut + ); + expect($(link)).toHandleWith( + 'mouseout', videoCaption.captionMouseOverOut + ); + expect($(link)).toHandleWith( + 'mousedown', videoCaption.captionMouseDown + ); + expect($(link)).toHandleWith( + 'click', videoCaption.captionClick + ); + expect($(link)).toHandleWith( + 'focus', videoCaption.captionFocus + ); + expect($(link)).toHandleWith( + 'blur', videoCaption.captionBlur + ); + expect($(link)).toHandleWith( + 'keydown', videoCaption.captionKeyDown + ); + }); + }); + + it('set rendered to true', function () { + expect(videoCaption.rendered).toBeTruthy(); + }); + + it('set playing to true', function () { + expect(videoCaption.playing).toBeTruthy(); + }); + }); + }); + + describe('pause', function () { + beforeEach(function () { + videoCaption.playing = true; + videoCaption.pause(); + }); + + it('set playing to false', function () { + expect(videoCaption.playing).toBeFalsy(); + }); + }); + + describe('updatePlayTime', function () { + describe('when the video speed is 1.0x', function () { + beforeEach(function () { + videoSpeedControl.currentSpeed = '1.0'; + videoCaption.updatePlayTime(25.000); + }); + + it('search the caption based on time', function () { + expect(videoCaption.currentIndex).toEqual(5); + }); + }); + + describe('when the video speed is not 1.0x', function () { + beforeEach(function () { + videoSpeedControl.currentSpeed = '0.75'; + videoCaption.updatePlayTime(25.000); + }); + + it('search the caption based on 1.0x speed', function () { + expect(videoCaption.currentIndex).toEqual(5); + }); + }); + + describe('when the index is not the same', function () { + beforeEach(function () { + videoCaption.currentIndex = 1; + $('.subtitles li[data-index=5]').addClass('current'); + videoCaption.updatePlayTime(25.000); + }); + + it('deactivate the previous caption', function () { + expect($('.subtitles li[data-index=1]')) + .not.toHaveClass('current'); + }); + + it('activate new caption', function () { + expect($('.subtitles li[data-index=5]')) + .toHaveClass('current'); + }); + + it('save new index', function () { + expect(videoCaption.currentIndex).toEqual(5); + }); + + it('scroll caption to new position', function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + + describe('when the index is the same', function () { + beforeEach(function () { + videoCaption.currentIndex = 1; + $('.subtitles li[data-index=3]').addClass('current'); + videoCaption.updatePlayTime(15.000); + }); + + it('does not change current subtitle', function () { + expect($('.subtitles li[data-index=3]')) + .toHaveClass('current'); + }); + }); + }); + + describe('resize', function () { + beforeEach(function () { + initialize(); + $('.subtitles li[data-index=1]').addClass('current'); + videoCaption.resize(); + }); + + describe('set the height of caption container', function () { + // Temporarily disabled due to intermittent failures + // with error "Expected 745 to be close to 805, 2." in Firefox + xit('when CC button is enabled', function () { + var realHeight = parseInt( + $('.subtitles').css('maxHeight'), 10 + ), + shouldBeHeight = $('.video-wrapper').height(); + + // Because of some problems with rounding on different + // environments: Linux * Mac * FF * Chrome + expect(realHeight).toBeCloseTo(shouldBeHeight, 2); + }); + + it('when CC button is disabled ', function () { + var realHeight, videoWrapperHeight, progressSliderHeight, + controlHeight, shouldBeHeight; + + state.captionsHidden = true; + videoCaption.setSubtitlesHeight(); + + realHeight = parseInt( + $('.subtitles').css('maxHeight'), 10 + ); + videoWrapperHeight = $('.video-wrapper').height(); + progressSliderHeight = videoControl.sliderEl.height(); + controlHeight = videoControl.el.height(); + shouldBeHeight = videoWrapperHeight - + 0.5 * progressSliderHeight - + controlHeight; + + expect(realHeight).toBe(shouldBeHeight); + }); + }); + + it('set the height of caption spacing', function () { + var firstSpacing, lastSpacing; + + firstSpacing = Math.abs(parseInt( + $('.subtitles .spacing:first').css('height'), 10 + )); + lastSpacing = Math.abs(parseInt( + $('.subtitles .spacing:last').css('height'), 10 + )); + + expect(firstSpacing - videoCaption.topSpacingHeight()) + .toBeLessThan(1); + expect(lastSpacing - videoCaption.bottomSpacingHeight()) + .toBeLessThan(1); + }); + + it('scroll caption to new position', function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + + describe('scrollCaption', function () { + beforeEach(function () { + initialize(); + }); + + describe('when frozen', function () { + beforeEach(function () { + videoCaption.frozen = true; + $('.subtitles li[data-index=1]').addClass('current'); + videoCaption.scrollCaption(); + }); + + it('does not scroll the caption', function () { + expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); + }); + + describe('when not frozen', function () { + beforeEach(function () { + videoCaption.frozen = false; + }); + + describe('when there is no current caption', function () { + beforeEach(function () { + videoCaption.scrollCaption(); + }); + + it('does not scroll the caption', function () { + expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); + }); + + describe('when there is a current caption', function () { + beforeEach(function () { + $('.subtitles li[data-index=1]').addClass('current'); + videoCaption.scrollCaption(); + }); + + it('scroll to current caption', function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + }); + }); + + describe('seekPlayer', function () { + describe('when the video speed is 1.0x', function () { + beforeEach(function () { + videoSpeedControl.currentSpeed = '1.0'; + $('.subtitles li[data-start="14910"]').trigger('click'); + }); + + // Temporarily disabled due to intermittent failures + // Fails with error: "InvalidStateError: An attempt was made to + // use an object that is not, or is no longer, usable + // Expected 0 to equal 14.91." + // on Firefox + xit('trigger seek event with the correct time', function () { + expect(videoPlayer.currentTime).toEqual(14.91); + }); + }); + + describe('when the video speed is not 1.0x', function () { + beforeEach(function () { + initialize(); + videoSpeedControl.currentSpeed = '0.75'; + $('.subtitles li[data-start="14910"]').trigger('click'); + }); + + it('trigger seek event with the correct time', function () { + expect(videoPlayer.currentTime).toEqual(14.91); + }); + }); + + describe('when the player type is Flash at speed 0.75x', + function () { + + beforeEach(function () { + initialize(); + videoSpeedControl.currentSpeed = '0.75'; + state.currentPlayerMode = 'flash'; + $('.subtitles li[data-start="14910"]').trigger('click'); + }); + + it('trigger seek event with the correct time', function () { + expect(videoPlayer.currentTime).toEqual(15); + }); + }); + }); + + describe('toggle', function () { + beforeEach(function () { + initialize(); + spyOn(videoPlayer, 'log'); + $('.subtitles li[data-index=1]').addClass('current'); + }); + + describe('when the caption is visible', function () { + beforeEach(function () { + state.el.removeClass('closed'); + videoCaption.toggle(jQuery.Event('click')); + }); + + it('log the hide_transcript event', function () { + expect(videoPlayer.log).toHaveBeenCalledWith( + 'hide_transcript', + { + currentTime: videoPlayer.currentTime + } + ); + }); + + it('hide the caption', function () { + expect(state.el).toHaveClass('closed'); + }); + }); + + describe('when the caption is hidden', function () { + beforeEach(function () { + state.el.addClass('closed'); + videoCaption.toggle(jQuery.Event('click')); + }); + + it('log the show_transcript event', function () { + expect(videoPlayer.log).toHaveBeenCalledWith( + 'show_transcript', + { + currentTime: videoPlayer.currentTime + } + ); + }); + + it('show the caption', function () { + expect(state.el).not.toHaveClass('closed'); + }); + + it('scroll the caption', function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + }); + + describe('caption accessibility', function () { + beforeEach(function () { + initialize(); + }); + + describe('when getting focus through TAB key', function () { + beforeEach(function () { + videoCaption.isMouseFocus = false; + $('.subtitles li[data-index=0]').trigger( + jQuery.Event('focus') + ); + }); + + it('shows an outline around the caption', function () { + expect($('.subtitles li[data-index=0]')) + .toHaveClass('focused'); + }); + + it('has automatic scrolling disabled', function () { + expect(videoCaption.autoScrolling).toBe(false); + }); + }); + + describe('when loosing focus through TAB key', function () { + beforeEach(function () { + $('.subtitles li[data-index=0]').trigger( + jQuery.Event('blur') + ); + }); + + it('does not show an outline around the caption', function () { + expect($('.subtitles li[data-index=0]')) + .not.toHaveClass('focused'); + }); + + it('has automatic scrolling enabled', function () { + expect(videoCaption.autoScrolling).toBe(true); + }); + }); + + describe( + 'when same caption gets the focus through mouse after ' + + 'having focus through TAB key', + function () { + + beforeEach(function () { + videoCaption.isMouseFocus = false; + $('.subtitles li[data-index=0]') + .trigger(jQuery.Event('focus')); + $('.subtitles li[data-index=0]') + .trigger(jQuery.Event('mousedown')); + }); + + it('does not show an outline around it', function () { + expect($('.subtitles li[data-index=0]')) + .not.toHaveClass('focused'); + }); + + it('has automatic scrolling enabled', function () { + expect(videoCaption.autoScrolling).toBe(true); + }); + }); + + describe( + 'when a second caption gets focus through mouse after ' + + 'first had focus through TAB key', + function () { + + var subDataLiIdx__0, subDataLiIdx__1; + + beforeEach(function () { + subDataLiIdx__0 = $('.subtitles li[data-index=0]'); + subDataLiIdx__1 = $('.subtitles li[data-index=1]'); + + videoCaption.isMouseFocus = false; + + subDataLiIdx__0.trigger(jQuery.Event('focus')); + subDataLiIdx__0.trigger(jQuery.Event('blur')); + + videoCaption.isMouseFocus = true; + + subDataLiIdx__1.trigger(jQuery.Event('mousedown')); + }); + + it('does not show an outline around the first', function () { + expect(subDataLiIdx__0).not.toHaveClass('focused'); + }); + + it('does not show an outline around the second', function () { + expect(subDataLiIdx__1).not.toHaveClass('focused'); + }); + + it('has automatic scrolling enabled', function () { + expect(videoCaption.autoScrolling).toBe(true); + }); + }); + + xdescribe('when enter key is pressed on a caption', function () { + var subDataLiIdx__0; + + beforeEach(function () { + var e; + + subDataLiIdx__0 = $('.subtitles li[data-index=0]'); + + spyOn(videoCaption, 'seekPlayer').andCallThrough(); + videoCaption.isMouseFocus = false; + subDataLiIdx__0.trigger(jQuery.Event('focus')); + e = jQuery.Event('keydown'); + e.which = 13; // ENTER key + subDataLiIdx__0.trigger(e); + }); + + // Temporarily disabled due to intermittent failures. + // + // Fails with error: "InvalidStateError: InvalidStateError: An + // attempt was made to use an object that is not, or is no + // longer, usable". + xit('shows an outline around it', function () { + expect(subDataLiIdx__0).toHaveClass('focused'); + }); + + xit('calls seekPlayer', function () { + expect(videoCaption.seekPlayer).toHaveBeenCalled(); + }); + }); }); - }); }); - describe('toggle', function() { - beforeEach(function() { - initialize(); - spyOn(videoPlayer, 'log'); - $('.subtitles li[data-index=1]').addClass('current'); - }); - - describe('when the caption is visible', function() { - beforeEach(function() { - state.el.removeClass('closed'); - videoCaption.toggle(jQuery.Event('click')); - }); - - it('log the hide_transcript event', function() { - expect(videoPlayer.log).toHaveBeenCalledWith('hide_transcript', { - currentTime: videoPlayer.currentTime - }); - }); - - it('hide the caption', function() { - expect(state.el).toHaveClass('closed'); - }); - }); - - describe('when the caption is hidden', function() { - beforeEach(function() { - state.el.addClass('closed'); - videoCaption.toggle(jQuery.Event('click')); - }); - - it('log the show_transcript event', function() { - expect(videoPlayer.log).toHaveBeenCalledWith('show_transcript', { - currentTime: videoPlayer.currentTime - }); - }); - - it('show the caption', function() { - expect(state.el).not.toHaveClass('closed'); - }); - - it('scroll the caption', function() { - expect($.fn.scrollTo).toHaveBeenCalled(); - }); - }); - }); - - describe('caption accessibility', function() { - beforeEach(function() { - initialize(); - }); - - describe('when getting focus through TAB key', function() { - beforeEach(function() { - videoCaption.isMouseFocus = false; - $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus')); - }); - - it('shows an outline around the caption', function() { - expect($('.subtitles li[data-index=0]')).toHaveClass('focused'); - }); - - it('has automatic scrolling disabled', function() { - expect(videoCaption.autoScrolling).toBe(false); - }); - }); - - describe('when loosing focus through TAB key', function() { - beforeEach(function() { - $('.subtitles li[data-index=0]').trigger(jQuery.Event('blur')); - }); - - it('does not show an outline around the caption', function() { - expect($('.subtitles li[data-index=0]')).not.toHaveClass('focused'); - }); - - it('has automatic scrolling enabled', function() { - expect(videoCaption.autoScrolling).toBe(true); - }); - }); - - describe('when same caption gets the focus through mouse after having focus through TAB key', function() { - beforeEach(function() { - videoCaption.isMouseFocus = false; - $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus')); - $('.subtitles li[data-index=0]').trigger(jQuery.Event('mousedown')); - }); - - it('does not show an outline around it', function() { - expect($('.subtitles li[data-index=0]')).not.toHaveClass('focused'); - }); - - it('has automatic scrolling enabled', function() { - expect(videoCaption.autoScrolling).toBe(true); - }); - }); - - describe('when a second caption gets focus through mouse after first had focus through TAB key', function() { - beforeEach(function() { - videoCaption.isMouseFocus = false; - $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus')); - $('.subtitles li[data-index=0]').trigger(jQuery.Event('blur')); - videoCaption.isMouseFocus = true; - $('.subtitles li[data-index=1]').trigger(jQuery.Event('mousedown')); - }); - - it('does not show an outline around the first', function() { - expect($('.subtitles li[data-index=0]')).not.toHaveClass('focused'); - }); - - it('does not show an outline around the second', function() { - expect($('.subtitles li[data-index=1]')).not.toHaveClass('focused'); - }); - - it('has automatic scrolling enabled', function() { - expect(videoCaption.autoScrolling).toBe(true); - }); - }); - - xdescribe('when enter key is pressed on a caption', function() { - beforeEach(function() { - var e; - spyOn(videoCaption, 'seekPlayer').andCallThrough(); - videoCaption.isMouseFocus = false; - $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus')); - e = jQuery.Event('keydown'); - e.which = 13; // ENTER key - $('.subtitles li[data-index=0]').trigger(e); - }); - - // Temporarily disabled due to intermittent failures - // Fails with error: "InvalidStateError: InvalidStateError: An attempt - // was made to use an object that is not, or is no longer, usable" - xit('shows an outline around it', function() { - expect($('.subtitles li[data-index=0]')).toHaveClass('focused'); - }); - - xit('calls seekPlayer', function() { - expect(videoCaption.seekPlayer).toHaveBeenCalled(); - }); - }); - }); - }); - }).call(this); From a1e0f7116a63859d702f372d7a28c6dbbf851303 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Mon, 30 Sep 2013 17:53:32 +0300 Subject: [PATCH 03/75] Added hard-coded flag to video.html that turns on/off autohiding of captions. Front-end functionality was not removed. When flag is set to "True", old behaviour of autohiding of controls and captions will be enabled. --- .../xmodule/xmodule/js/src/video/01_initialize.js | 7 +++++++ .../xmodule/js/src/video/04_video_control.js | 4 ++-- .../xmodule/js/src/video/09_video_caption.js | 6 +++--- lms/templates/video.html | 13 +++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js index d9f0addabf..2a25240678 100644 --- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js @@ -295,6 +295,11 @@ function (VideoPlayer) { ), youtubeStreams: this.el.data('streams'), + autohideHtml5: ( + this.el.data('autohide-html5') + .toString().toLowerCase() === 'true' + ), + sub: this.el.data('sub'), mp4Source: this.el.data('mp4-source'), webmSource: this.el.data('webm-source'), @@ -307,6 +312,8 @@ function (VideoPlayer) { availableQualities: ['hd720', 'hd1080', 'highres'] }; + console.log('this.config.autohideHtml5 = ' + this.config.autohideHtml5); + // Check if the YT test timeout has been set. If not, or it is in // improper format, then set to default value. tempYtTestTimeout = parseInt(this.el.data('yt-test-timeout'), 10); diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js index 796ba07060..8ba7490cef 100644 --- a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js @@ -57,7 +57,7 @@ function () { state.videoControl.play(); } - if (state.videoType === 'html5') { + if ((state.videoType === 'html5') && (state.config.autohideHtml5)) { state.videoControl.fadeOutTimeout = state.config.fadeOutTimeout; state.videoControl.el.addClass('html5'); @@ -81,7 +81,7 @@ function () { state.videoControl.fullScreenEl.on('click', state.videoControl.toggleFullScreen); $(document).on('keyup', state.videoControl.exitFullScreen); - if (state.videoType === 'html5') { + if ((state.videoType === 'html5') && (state.config.autohideHtml5)) { state.el.on('mousemove', state.videoControl.showControls); state.el.on('keydown', state.videoControl.showControls); } diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 13f2fe0db0..54dee1fc7f 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -152,7 +152,7 @@ function () { this.videoCaption.onMovement ); - if (this.videoType === 'html5') { + if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { this.el.on('mousemove', this.videoCaption.autoShowCaptions); this.el.on('keydown', this.videoCaption.autoShowCaptions); @@ -335,7 +335,7 @@ function () { this.videoCaption.setSubtitlesHeight(); - if (this.videoType === 'html5') { + if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { this.videoCaption.fadeOutTimeout = this.config.fadeOutTimeout; this.videoCaption.subtitlesEl.addClass('html5'); @@ -698,7 +698,7 @@ function () { function setSubtitlesHeight() { var height = 0; - if (this.videoType === 'html5'){ + if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { // on page load captionHidden = undefined if ( ( diff --git a/lms/templates/video.html b/lms/templates/video.html index caf0aaa06f..d0e61a5f74 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -26,6 +26,19 @@ data-yt-test-timeout="${yt_test_timeout}" data-yt-test-url="${yt_test_url}" + ## For now, the option "data-autohide-html5" is hard coded. This option + ## either enables or disables autohiding of controls and captions on mouse + ## inactivity. If set to true, controls and captions will autohide for + ## HTML5 sources (non-YouTube) after a period of mouse inactivity over the + ## whole video. When the mouse moves (or a key is pressed while any part of + ## the video player is focused), the captions and controls will be shown + ## once again. + ## + ## There is no option in the "Advanced Editor" to set this option. However, + ## this option will have an effect if changed to "True". The code on + ## front-end exists. + data-autohide-html5="False" + tabindex="-1" >
    From 49f4a71178712661b99a7f45c5de634195b7d95c Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Tue, 1 Oct 2013 11:28:13 +0300 Subject: [PATCH 04/75] Added functionality to show captions on "CC" button mousemove. When auto-show/auto-hide is disabled, the captions will be shown when the user will move the mouse over the "CC" button. They will then auto-hide after a while if the user doesn't continue to move the mouse over the "CC" button, or over the captions themselves, or use the keyboard to select a specific caption. If the mouse pointer is not over the captions or the "CC" button, they will hide after a while. This is the key in the fix for bug BLD-355: Transcript hovers over over videoplayer itself for PKU videos. --- .../xmodule/js/src/video/01_initialize.js | 2 - .../xmodule/js/src/video/09_video_caption.js | 46 ++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js index 2a25240678..d887bd6c09 100644 --- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js @@ -312,8 +312,6 @@ function (VideoPlayer) { availableQualities: ['hd720', 'hd1080', 'highres'] }; - console.log('this.config.autohideHtml5 = ' + this.config.autohideHtml5); - // Check if the YT test timeout has been set. If not, or it is in // improper format, then set to default value. tempYtTestTimeout = parseInt(this.el.data('yt-test-timeout'), 10); diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 54dee1fc7f..d1f3cf071f 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -157,13 +157,25 @@ function () { this.el.on('keydown', this.videoCaption.autoShowCaptions); // Moving slider on subtitles is not a mouse move, - // but captions and controls should be showed. + // but captions and controls should be shown. this.videoCaption.subtitlesEl.on( 'scroll', this.videoCaption.autoShowCaptions ); this.videoCaption.subtitlesEl.on( 'scroll', this.videoControl.showControls ); + } else if (!this.config.autohideHtml5) { + // this.videoCaption.subtitlesEl.on('mousemove', this.videoCaption.autoShowCaptions); + this.videoCaption.subtitlesEl.on('keydown', this.videoCaption.autoShowCaptions); + + this.videoCaption.hideSubtitlesEl.on('mousemove', this.videoCaption.autoShowCaptions); + this.videoCaption.hideSubtitlesEl.on('keydown', this.videoCaption.autoShowCaptions); + + // Moving slider on subtitles is not a mouse move, + // but captions should not be auto-hidden. + this.videoCaption.subtitlesEl.on( + 'scroll', this.videoCaption.autoShowCaptions + ); } } @@ -322,6 +334,10 @@ function () { } function onMovement() { + if (!this.config.autohideHtml5) { + this.videoCaption.autoShowCaptions(); + } + this.videoCaption.onMouseEnter(); } @@ -343,6 +359,14 @@ function () { this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout ); + } else if (!this.config.autohideHtml5) { + this.videoCaption.fadeOutTimeout = this.config.fadeOutTimeout; + this.videoCaption.subtitlesEl.addClass('html5'); + + this.captionHideTimeout = setTimeout( + this.videoCaption.autoHideCaptions, + 0 + ); } this.videoCaption.hideCaptions(this.hide_captions); @@ -638,6 +662,21 @@ function () { this.videoCaption.hideCaptions(false); } else { this.videoCaption.hideCaptions(true); + + // In the case when captions are not auto-hidden based on mouse + // movement anywhere on the video, we must hide them explicitly + // after the "CC" button has been clicked (to hide captions). + // + // Otherwise, in order for the captions to disappear again, the + // user must move the mouse button over the "CC" button, or over + // the captions themselves. In this case, an "autoShow" will be + // triggered, and after a timeout, an "autoHide". + if (!this.config.autohideHtml5) { + this.captionHideTimeout = setTimeout( + this.videoCaption.autoHideCaptions(), + 0 + ); + } } } @@ -698,7 +737,10 @@ function () { function setSubtitlesHeight() { var height = 0; - if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { + if ( + ((this.videoType === 'html5') && (this.config.autohideHtml5)) || + (!this.config.autohideHtml5) + ){ // on page load captionHidden = undefined if ( ( From 2a6418ec045a114a6d6d32438c5d0325c3561a7c Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Tue, 1 Oct 2013 11:44:43 +0300 Subject: [PATCH 05/75] Updated fixtures for Jasmine tests. --- common/lib/xmodule/xmodule/js/fixtures/video.html | 2 ++ common/lib/xmodule/xmodule/js/fixtures/video_all.html | 2 ++ common/lib/xmodule/xmodule/js/fixtures/video_html5.html | 2 ++ .../lib/xmodule/xmodule/js/fixtures/video_no_captions.html | 2 ++ .../lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html | 6 ++++++ 5 files changed, 14 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/fixtures/video.html b/common/lib/xmodule/xmodule/js/fixtures/video.html index f607430ba0..675d750bcb 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video.html @@ -12,6 +12,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_all.html b/common/lib/xmodule/xmodule/js/fixtures/video_all.html index 57052bf65d..41f3f94ad5 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_all.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_all.html @@ -15,6 +15,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html index 32789b6ba9..d213b5a4b1 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html @@ -15,6 +15,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html index 61975784c1..f125f19777 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html @@ -12,6 +12,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html index c6b40cdf16..c13d55713f 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html @@ -12,6 +12,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    @@ -73,6 +75,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    @@ -130,6 +134,8 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" + + data-autohide-html5="True" >
    From e1e0cba033048a4b5e421e6a30e37f7ca7d89743 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Wed, 2 Oct 2013 15:41:23 +0300 Subject: [PATCH 06/75] Added acceptance tests. --- .../contentstore/features/video.feature | 22 ++++++++++++++++ cms/djangoapps/contentstore/features/video.py | 26 +++++++++++++++++++ .../courseware/features/video.feature | 7 +++++ 3 files changed, 55 insertions(+) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index ad6fea083b..f711092c40 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -2,15 +2,18 @@ Feature: CMS.Video Component As a course author, I want to be able to view my created videos in Studio. + # 1 # Video Alpha Features will work in Firefox only when Firefox is the active window Scenario: Autoplay is disabled in Studio Given I have created a Video component Then when I view the video it does not have autoplay enabled + # 2 Scenario: Creating a video takes a single click Given I have clicked the new unit button Then creating a video takes a single click + # 3 # Sauce Labs cannot delete cookies @skip_sauce Scenario: Captions are hidden correctly @@ -18,12 +21,14 @@ Feature: CMS.Video Component And I have hidden captions Then when I view the video it does not show the captions + # 4 # Sauce Labs cannot delete cookies @skip_sauce Scenario: Captions are shown correctly Given I have created a Video component with subtitles Then when I view the video it does show the captions + # 5 # Sauce Labs cannot delete cookies @skip_sauce Scenario: Captions are toggled correctly @@ -31,6 +36,23 @@ Feature: CMS.Video Component And I have toggled captions Then when I view the video it does show the captions + # 6 Scenario: Video data is shown correctly Given I have created a video with only XML data Then the correct Youtube video is shown + + # 7 + Scenario: Closed captions become visible when the mouse hovers over CC button + Given I have created a Video component with subtitles + And Make sure captions are closed + Then Captions become invisible + And Hover over CC button + Then Captions become visible + + # 8 + Scenario: Open captions never become invisible + Given I have created a Video component with subtitles + And Make sure captions are open + Then Captions become visible + And Hover over CC button + Then Captions become visible diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index 59c5f2bab9..877b6e44ca 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -108,3 +108,29 @@ def the_youtube_video_is_shown(_step): ele = world.css_find('.video').first assert ele['data-streams'].split(':')[1] == world.scenario_dict['YOUTUBE_ID'] + +@step('Make sure captions are (.+)$') +def make_sure_captions_are_closed(_step, captions_state): + if captions_state == 'closed': + if world.css_visible('.subtitles'): + world.browser.find_by_css('.hide-subtitles').click() + else: + if not world.css_visible('.subtitles'): + world.browser.find_by_css('.hide-subtitles').click() + + +@step('Hover over CC button$') +def hover_over_cc_button(_step): + world.browser.find_by_css('.hide-subtitles').mouse_over() + + +@step('Captions become (.+)$') +def captions_become_visible(_step, visibility_state): + # Captions become invisible by fading out. We must wait. + world.wait(2) + + if visibility_state == 'visible': + assert world.css_visible('.subtitles') + else: + assert not world.css_visible('.subtitles') + diff --git a/lms/djangoapps/courseware/features/video.feature b/lms/djangoapps/courseware/features/video.feature index 001481a6a5..7518bf6bdb 100644 --- a/lms/djangoapps/courseware/features/video.feature +++ b/lms/djangoapps/courseware/features/video.feature @@ -2,38 +2,45 @@ Feature: LMS.Video component As a student, I want to view course videos in LMS. + # 1 Scenario: Video component is fully rendered in the LMS in HTML5 mode Given the course has a Video component in HTML5 mode Then when I view the video it has rendered in HTML5 mode And all sources are correct + # 2 # Firefox doesn't have HTML5 (only mp4 - fix here) @skip_firefox Scenario: Autoplay is disabled in LMS for a Video component Given the course has a Video component in HTML5 mode Then when I view the video it does not have autoplay enabled + # 3 # Youtube testing Scenario: Video component is fully rendered in the LMS in Youtube mode with HTML5 sources Given youtube server is up and response time is 0.4 seconds And the course has a Video component in Youtube_HTML5 mode Then when I view the video it has rendered in Youtube mode + # 4 Scenario: Video component is not rendered in the LMS in Youtube mode with HTML5 sources Given youtube server is up and response time is 2 seconds And the course has a Video component in Youtube_HTML5 mode Then when I view the video it has rendered in HTML5 mode + # 5 Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources Given youtube server is up and response time is 2 seconds And the course has a Video component in Youtube mode Then when I view the video it has rendered in Youtube mode + # 6 Scenario: Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser Given youtube server is up and response time is 2 seconds And the course has a Video component in Youtube_HTML5_Unsupported_Video mode Then when I view the video it has rendered in Youtube mode + # 7 Scenario: Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser Given the course has a Video component in HTML5_Unsupported_Video mode Then error message is shown From 1f84053046680fa51b5df2324ae8cc7aae406c87 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Wed, 2 Oct 2013 15:50:21 +0300 Subject: [PATCH 07/75] Updated acceptance tests. --- cms/djangoapps/contentstore/features/video.feature | 2 ++ cms/djangoapps/contentstore/features/video.py | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index f711092c40..4173dc499a 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -56,3 +56,5 @@ Feature: CMS.Video Component Then Captions become visible And Hover over CC button Then Captions become visible + And Hover over volume button + Then Captions become visible diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index 877b6e44ca..af7ee2396f 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -110,7 +110,7 @@ def the_youtube_video_is_shown(_step): @step('Make sure captions are (.+)$') -def make_sure_captions_are_closed(_step, captions_state): +def set_captions_visibility_state(_step, captions_state): if captions_state == 'closed': if world.css_visible('.subtitles'): world.browser.find_by_css('.hide-subtitles').click() @@ -119,9 +119,12 @@ def make_sure_captions_are_closed(_step, captions_state): world.browser.find_by_css('.hide-subtitles').click() -@step('Hover over CC button$') -def hover_over_cc_button(_step): - world.browser.find_by_css('.hide-subtitles').mouse_over() +@step('Hover over (.+) button$') +def hover_over_button(_step, button): + if button.strip() == 'CC': + world.browser.find_by_css('.hide-subtitles').mouse_over() + else: + world.browser.find_by_css('.volume').mouse_over() @step('Captions become (.+)$') From 55fde6e5a7d9da736a61987c56a12de6460d98af Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Wed, 2 Oct 2013 17:26:56 +0300 Subject: [PATCH 08/75] Updated acceptance tests. --- .../contentstore/features/video.feature | 20 ++++++++++++++----- cms/djangoapps/contentstore/features/video.py | 16 +++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index 4173dc499a..e241ff654b 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -45,16 +45,26 @@ Feature: CMS.Video Component Scenario: Closed captions become visible when the mouse hovers over CC button Given I have created a Video component with subtitles And Make sure captions are closed - Then Captions become invisible + Then Captions become invisible after 3 seconds And Hover over CC button - Then Captions become visible + Then Captions become visible after 0 seconds + And Hover over volume button + Then Captions become invisible after 3 seconds # 8 Scenario: Open captions never become invisible Given I have created a Video component with subtitles And Make sure captions are open - Then Captions become visible + Then Captions are visible after 0 seconds And Hover over CC button - Then Captions become visible + Then Captions are visible after 3 seconds And Hover over volume button - Then Captions become visible + Then Captions are visible after 3 seconds + + # 9 + Scenario: Closed captions are invisible when mouse doesn't hover on CC button + Given I have created a Video component with subtitles + And Make sure captions are closed + Then Captions become invisible after 3 seconds + And Hover over volume button + Then Captions are invisible after 0 seconds diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index af7ee2396f..a58455aa28 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -127,13 +127,21 @@ def hover_over_button(_step, button): world.browser.find_by_css('.volume').mouse_over() -@step('Captions become (.+)$') -def captions_become_visible(_step, visibility_state): - # Captions become invisible by fading out. We must wait. - world.wait(2) +@step('Captions become (.+) after (.+) seconds$') +def check_captions_visibility_state(_step, visibility_state, timeout): + timeout = int(timeout.strip()) + + # Captions become invisible by fading out. We must wait by a specified + # time. + world.wait(timeout) if visibility_state == 'visible': assert world.css_visible('.subtitles') else: assert not world.css_visible('.subtitles') + +@step('Captions are (.+) after (.+) seconds$') +def check_captions_visibility_state2(_step, visibility_state, timeout): + check_captions_visibility_state(_step, visibility_state, timeout) + From 05eb13f0866448c9e8e584d5bfcfae0d8921eeca Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 3 Oct 2013 16:42:19 +0300 Subject: [PATCH 09/75] Fixing JS. There were previous merge conflicts in the file. --- .../xmodule/js/src/video/09_video_caption.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index d1f3cf071f..ef70127ccf 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -165,11 +165,16 @@ function () { 'scroll', this.videoControl.showControls ); } else if (!this.config.autohideHtml5) { - // this.videoCaption.subtitlesEl.on('mousemove', this.videoCaption.autoShowCaptions); - this.videoCaption.subtitlesEl.on('keydown', this.videoCaption.autoShowCaptions); + this.videoCaption.subtitlesEl.on( + 'keydown', this.videoCaption.autoShowCaptions + ); - this.videoCaption.hideSubtitlesEl.on('mousemove', this.videoCaption.autoShowCaptions); - this.videoCaption.hideSubtitlesEl.on('keydown', this.videoCaption.autoShowCaptions); + this.videoCaption.hideSubtitlesEl.on( + 'mousemove', this.videoCaption.autoShowCaptions + ); + this.videoCaption.hideSubtitlesEl.on( + 'keydown', this.videoCaption.autoShowCaptions + ); // Moving slider on subtitles is not a mouse move, // but captions should not be auto-hidden. From 3b7cc6a419f17b0f8b7bfd0754df8aefd88e500f Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 3 Oct 2013 16:50:23 +0300 Subject: [PATCH 10/75] Furthe addressing comments on PR. --- .../xmodule/xmodule/js/fixtures/video.html | 1 - .../xmodule/js/fixtures/video_all.html | 1 - .../xmodule/js/fixtures/video_html5.html | 1 - .../js/fixtures/video_no_captions.html | 1 - .../js/fixtures/video_yt_multiple.html | 1 - .../xmodule/js/src/video/09_video_caption.js | 43 +++++++++---------- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/fixtures/video.html b/common/lib/xmodule/xmodule/js/fixtures/video.html index 675d750bcb..e658912885 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video.html @@ -12,7 +12,6 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" - data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_all.html b/common/lib/xmodule/xmodule/js/fixtures/video_all.html index 41f3f94ad5..b774134cf7 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_all.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_all.html @@ -15,7 +15,6 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" - data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html index d213b5a4b1..fcb5a3c319 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html @@ -15,7 +15,6 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" - data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html index f125f19777..ceb24299e9 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html @@ -12,7 +12,6 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" - data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html index c13d55713f..bf9272d230 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html @@ -12,7 +12,6 @@ data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" - data-autohide-html5="True" >
    diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index ef70127ccf..06a5ba7565 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -153,34 +153,33 @@ function () { ); if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { - this.el.on('mousemove', this.videoCaption.autoShowCaptions); - this.el.on('keydown', this.videoCaption.autoShowCaptions); + this.el.on({ + mousemove, this.videoCaption.autoShowCaptions, + keydown, this.videoCaption.autoShowCaptions + }); // Moving slider on subtitles is not a mouse move, // but captions and controls should be shown. - this.videoCaption.subtitlesEl.on( - 'scroll', this.videoCaption.autoShowCaptions - ); - this.videoCaption.subtitlesEl.on( - 'scroll', this.videoControl.showControls - ); + this.videoCaption.subtitlesEl + .on( + 'scroll', this.videoCaption.autoShowCaptions + ) + .on( + 'scroll', this.videoControl.showControls + ); } else if (!this.config.autohideHtml5) { - this.videoCaption.subtitlesEl.on( - 'keydown', this.videoCaption.autoShowCaptions - ); + this.videoCaption.subtitlesEl.on({ + keydown: this.videoCaption.autoShowCaptions, - this.videoCaption.hideSubtitlesEl.on( - 'mousemove', this.videoCaption.autoShowCaptions - ); - this.videoCaption.hideSubtitlesEl.on( - 'keydown', this.videoCaption.autoShowCaptions - ); + // Moving slider on subtitles is not a mouse move, + // but captions should not be auto-hidden. + scroll: this.videoCaption.autoShowCaptions + }); - // Moving slider on subtitles is not a mouse move, - // but captions should not be auto-hidden. - this.videoCaption.subtitlesEl.on( - 'scroll', this.videoCaption.autoShowCaptions - ); + this.videoCaption.hideSubtitlesEl.on({ + mousemove: this.videoCaption.autoShowCaptions, + keydown: this.videoCaption.autoShowCaptions + }); } } From f17a84c96583e3dc426350e29032c6d224eed316 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 3 Oct 2013 16:57:00 +0300 Subject: [PATCH 11/75] Minor tweak. --- .../lib/xmodule/xmodule/js/src/video/09_video_caption.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 06a5ba7565..ebb8bfde43 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -158,8 +158,8 @@ function () { keydown, this.videoCaption.autoShowCaptions }); - // Moving slider on subtitles is not a mouse move, - // but captions and controls should be shown. + // Moving slider on subtitles is not a mouse move, but captions and + // controls should be shown. this.videoCaption.subtitlesEl .on( 'scroll', this.videoCaption.autoShowCaptions @@ -171,8 +171,8 @@ function () { this.videoCaption.subtitlesEl.on({ keydown: this.videoCaption.autoShowCaptions, - // Moving slider on subtitles is not a mouse move, - // but captions should not be auto-hidden. + // Moving slider on subtitles is not a mouse move, but captions + // should not be auto-hidden. scroll: this.videoCaption.autoShowCaptions }); From c3030e8f943dcd7dd1b280c2bab640edb43473a3 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 3 Oct 2013 17:03:03 +0300 Subject: [PATCH 12/75] Fixing minor typo. --- common/lib/xmodule/xmodule/js/src/video/09_video_caption.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index ebb8bfde43..26c0b4048c 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -154,8 +154,8 @@ function () { if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { this.el.on({ - mousemove, this.videoCaption.autoShowCaptions, - keydown, this.videoCaption.autoShowCaptions + mousemove: this.videoCaption.autoShowCaptions, + keydown: this.videoCaption.autoShowCaptions }); // Moving slider on subtitles is not a mouse move, but captions and From 0bd706b6a1c1da0358ba73d6e1be7be38ddae8b1 Mon Sep 17 00:00:00 2001 From: polesye Date: Fri, 4 Oct 2013 15:31:08 +0300 Subject: [PATCH 13/75] Tidy up the code. --- .../contentstore/features/video.feature | 16 ++-- cms/djangoapps/contentstore/features/video.py | 33 ++++---- .../xmodule/js/src/video/01_initialize.js | 46 +++++------ .../xmodule/js/src/video/09_video_caption.js | 76 ++++++++++--------- 4 files changed, 88 insertions(+), 83 deletions(-) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index e241ff654b..d29893a047 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -46,19 +46,19 @@ Feature: CMS.Video Component Given I have created a Video component with subtitles And Make sure captions are closed Then Captions become invisible after 3 seconds - And Hover over CC button - Then Captions become visible after 0 seconds - And Hover over volume button + And I hover over button "CC" + Then Captions become visible + And I hover over button "volume" Then Captions become invisible after 3 seconds # 8 Scenario: Open captions never become invisible Given I have created a Video component with subtitles And Make sure captions are open - Then Captions are visible after 0 seconds - And Hover over CC button + Then Captions are visible + And I hover over button "CC" Then Captions are visible after 3 seconds - And Hover over volume button + And I hover over button "volume" Then Captions are visible after 3 seconds # 9 @@ -66,5 +66,5 @@ Feature: CMS.Video Component Given I have created a Video component with subtitles And Make sure captions are closed Then Captions become invisible after 3 seconds - And Hover over volume button - Then Captions are invisible after 0 seconds + And I hover over button "volume" + Then Captions are invisible diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index a58455aa28..b5dba0a940 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -5,6 +5,11 @@ from terrain.steps import reload_the_page from xmodule.modulestore import Location from contentstore.utils import get_modulestore +BUTTONS = { + 'CC': '.hide-subtitles', + 'volume': '.volume', +} + @step('I have created a Video component$') def i_created_a_video_component(step): @@ -17,8 +22,13 @@ def i_created_a_video_component(step): @step('I have created a Video component with subtitles$') -def i_created_a_video_component_subtitles(step): - step.given('I have created a Video component') +def i_created_a_video_with_subs(_step): + _step.given('I have created a Video component with subtitles "OEoXaMPEzfM"') + + +@step('I have created a Video component with subtitles "([^"]*)"$') +def i_created_a_video_with_subs_with_name(_step, sub_id): + _step.given('I have created a Video component') # Store the current URL so we can return here video_url = world.browser.url @@ -119,15 +129,17 @@ def set_captions_visibility_state(_step, captions_state): world.browser.find_by_css('.hide-subtitles').click() -@step('Hover over (.+) button$') +@step('I hover over button "([^"]*)"$') def hover_over_button(_step, button): - if button.strip() == 'CC': - world.browser.find_by_css('.hide-subtitles').mouse_over() - else: - world.browser.find_by_css('.volume').mouse_over() + world.browser.find_by_css(BUTTONS[button.strip()]).mouse_over() -@step('Captions become (.+) after (.+) seconds$') +@step('Captions (?:are|become) (.+)$') +def are_captions_visibile(_step, visibility_state): + _step.given('Captions are {0} after 0 seconds'.format(visibility_state)) + + +@step('Captions (?:are|become) (.+) after (.+) seconds$') def check_captions_visibility_state(_step, visibility_state, timeout): timeout = int(timeout.strip()) @@ -140,8 +152,3 @@ def check_captions_visibility_state(_step, visibility_state, timeout): else: assert not world.css_visible('.subtitles') - -@step('Captions are (.+) after (.+) seconds$') -def check_captions_visibility_state2(_step, visibility_state, timeout): - check_captions_visibility_state(_step, visibility_state, timeout) - diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js index d887bd6c09..845d79ddb9 100644 --- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js @@ -264,7 +264,9 @@ function (VideoPlayer) { // The function set initial configuration and preparation. function initialize(element) { - var _this = this, tempYtTestTimeout; + var _this = this, + regExp = /^true$/i, + data, tempYtTestTimeout; // This is used in places where we instead would have to check if an // element has a CSS class 'fullscreen'. this.isFullScreen = false; @@ -274,6 +276,9 @@ function (VideoPlayer) { this.elVideoWrapper = this.el.find('.video-wrapper'); this.id = this.el.attr('id').replace(/video_/, ''); + // jQuery .data() return object with keys in lower camelCase format. + data = this.el.data(); + console.log( '[Video info]: Initializing video with id "' + this.id + '".' ); @@ -284,37 +289,26 @@ function (VideoPlayer) { this.config = { element: element, - start: this.el.data('start'), - end: this.el.data('end'), - - caption_data_dir: this.el.data('caption-data-dir'), - caption_asset_path: this.el.data('caption-asset-path'), - show_captions: ( - this.el.data('show-captions') - .toString().toLowerCase() === 'true' - ), - youtubeStreams: this.el.data('streams'), - - autohideHtml5: ( - this.el.data('autohide-html5') - .toString().toLowerCase() === 'true' - ), - - sub: this.el.data('sub'), - mp4Source: this.el.data('mp4-source'), - webmSource: this.el.data('webm-source'), - oggSource: this.el.data('ogg-source'), - - ytTestUrl: this.el.data('yt-test-url'), - + start: data['start'], + end: data['end'], + caption_data_dir: data['captionDataDir'], + caption_asset_path: data['captionAssetPath'], + show_captions: regExp.test(data['showCaptions'].toString()), + youtubeStreams: data['streams'], + autohideHtml5: regExp.test(data['autohideHtml5'].toString()), + sub: data['sub'], + mp4Source: data['mp4Source'], + webmSource: data['webmSource'], + oggSource: data['oggSource'], + ytTestUrl: data['ytTestUrl'], fadeOutTimeout: 1400, - + captionsFreezeTime: 10000, availableQualities: ['hd720', 'hd1080', 'highres'] }; // Check if the YT test timeout has been set. If not, or it is in // improper format, then set to default value. - tempYtTestTimeout = parseInt(this.el.data('yt-test-timeout'), 10); + tempYtTestTimeout = parseInt(data['ytTestTimeout'], 10); if (!isFinite(tempYtTestTimeout)) { tempYtTestTimeout = 1500; } diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 26c0b4048c..b11f08ce21 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -294,12 +294,13 @@ function () { _this = this; - this.videoCaption.subtitlesEl.fadeOut( - this.videoCaption.fadeOutTimeout, - function () { - - _this.captionState = 'invisible'; - }); + this.videoCaption.subtitlesEl + .fadeOut( + this.videoCaption.fadeOutTimeout, + function () { + _this.captionState = 'invisible'; + } + ); } function resize() { @@ -321,7 +322,7 @@ function () { this.videoCaption.frozen = setTimeout( this.videoCaption.onMouseLeave, - 10000 + this.config.captionsFreezeTime ); } @@ -391,17 +392,18 @@ function () { container.append(liEl); }); - this.videoCaption.subtitlesEl.html(container.html()); - - this.videoCaption.subtitlesEl.find('li[data-index]').on({ - mouseover: this.videoCaption.captionMouseOverOut, - mouseout: this.videoCaption.captionMouseOverOut, - mousedown: this.videoCaption.captionMouseDown, - click: this.videoCaption.captionClick, - focus: this.videoCaption.captionFocus, - blur: this.videoCaption.captionBlur, - keydown: this.videoCaption.captionKeyDown - }); + this.videoCaption.subtitlesEl + .html(container.html()) + .find('li[data-index]') + .on({ + mouseover: this.videoCaption.captionMouseOverOut, + mouseout: this.videoCaption.captionMouseOverOut, + mousedown: this.videoCaption.captionMouseDown, + click: this.videoCaption.captionClick, + focus: this.videoCaption.captionFocus, + blur: this.videoCaption.captionBlur, + keydown: this.videoCaption.captionKeyDown + }); // Enables or disables automatic scrolling of the captions when the // video is playing. This feature has to be disabled when tabbing @@ -422,14 +424,15 @@ function () { // outline has to be drawn (tabbing) or not (mouse click). this.videoCaption.isMouseFocus = false; - this.videoCaption.subtitlesEl.prepend( - $('
  5. ') - .height(this.videoCaption.topSpacingHeight()) - ); - this.videoCaption.subtitlesEl.append( - $('
  6. ') - .height(this.videoCaption.bottomSpacingHeight()) - ); + this.videoCaption.subtitlesEl + .prepend( + $('
  7. ') + .height(this.videoCaption.topSpacingHeight()) + ) + .append( + $('
  8. ') + .height(this.videoCaption.bottomSpacingHeight()) + ); this.videoCaption.rendered = true; } @@ -684,26 +687,27 @@ function () { } } - function hideCaptions(hide_captions) { - var type; + function hideCaptions(hide_captions, update_cookie) { + var hideSubtitlesEl = this.videoCaption.hideSubtitlesEl, + type; if (hide_captions) { type = 'hide_transcript'; this.captionsHidden = true; - this.videoCaption.hideSubtitlesEl.attr( - 'title', gettext('Turn on captions') - ); - this.videoCaption.hideSubtitlesEl + + hideSubtitlesEl + .attr('title', gettext('Turn on captions')) .text(gettext('Turn on captions')); + this.el.addClass('closed'); } else { type = 'show_transcript'; this.captionsHidden = false; - this.videoCaption.hideSubtitlesEl.attr( - 'title', gettext('Turn off captions') - ); - this.videoCaption.hideSubtitlesEl + + hideSubtitlesEl + .attr('title', gettext('Turn off captions')) .text(gettext('Turn off captions')); + this.el.removeClass('closed'); this.videoCaption.scrollCaption(); } From 46abe397e2a1767a28100bc9330dace6607dcc3e Mon Sep 17 00:00:00 2001 From: polesye Date: Fri, 4 Oct 2013 16:09:02 +0300 Subject: [PATCH 14/75] Fix acceptance tests. --- .../contentstore/features/video.feature | 16 ++++++++-------- cms/djangoapps/contentstore/features/video.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index d29893a047..940d7aaba3 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -45,26 +45,26 @@ Feature: CMS.Video Component Scenario: Closed captions become visible when the mouse hovers over CC button Given I have created a Video component with subtitles And Make sure captions are closed - Then Captions become invisible after 3 seconds + Then Captions become "invisible" after 3 seconds And I hover over button "CC" - Then Captions become visible + Then Captions become "visible" And I hover over button "volume" - Then Captions become invisible after 3 seconds + Then Captions become "invisible" after 3 seconds # 8 Scenario: Open captions never become invisible Given I have created a Video component with subtitles And Make sure captions are open - Then Captions are visible + Then Captions are "visible" And I hover over button "CC" - Then Captions are visible after 3 seconds + Then Captions are "visible" after 3 seconds And I hover over button "volume" - Then Captions are visible after 3 seconds + Then Captions are "visible" after 3 seconds # 9 Scenario: Closed captions are invisible when mouse doesn't hover on CC button Given I have created a Video component with subtitles And Make sure captions are closed - Then Captions become invisible after 3 seconds + Then Captions become "invisible" after 3 seconds And I hover over button "volume" - Then Captions are invisible + Then Captions are "invisible" diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py index b5dba0a940..1b422294d0 100644 --- a/cms/djangoapps/contentstore/features/video.py +++ b/cms/djangoapps/contentstore/features/video.py @@ -131,15 +131,15 @@ def set_captions_visibility_state(_step, captions_state): @step('I hover over button "([^"]*)"$') def hover_over_button(_step, button): - world.browser.find_by_css(BUTTONS[button.strip()]).mouse_over() + world.css_find(BUTTONS[button.strip()]).mouse_over() -@step('Captions (?:are|become) (.+)$') +@step('Captions (?:are|become) "([^"]*)"$') def are_captions_visibile(_step, visibility_state): - _step.given('Captions are {0} after 0 seconds'.format(visibility_state)) + _step.given('Captions become "{0}" after 0 seconds'.format(visibility_state)) -@step('Captions (?:are|become) (.+) after (.+) seconds$') +@step('Captions (?:are|become) "([^"]*)" after (.+) seconds$') def check_captions_visibility_state(_step, visibility_state, timeout): timeout = int(timeout.strip()) From 2e216a2c144eb9d6ab98ecd347f8d2667be3852f Mon Sep 17 00:00:00 2001 From: polesye Date: Fri, 4 Oct 2013 16:38:16 +0300 Subject: [PATCH 15/75] Clean up the code. --- cms/djangoapps/contentstore/features/video.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature index 940d7aaba3..8f6ca144b8 100644 --- a/cms/djangoapps/contentstore/features/video.feature +++ b/cms/djangoapps/contentstore/features/video.feature @@ -57,9 +57,9 @@ Feature: CMS.Video Component And Make sure captions are open Then Captions are "visible" And I hover over button "CC" - Then Captions are "visible" after 3 seconds + Then Captions are "visible" And I hover over button "volume" - Then Captions are "visible" after 3 seconds + Then Captions are "visible" # 9 Scenario: Closed captions are invisible when mouse doesn't hover on CC button From 30ddaf77576f74651354c51682476c638a1baf42 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Mon, 7 Oct 2013 16:50:00 +0300 Subject: [PATCH 16/75] Enabling showing of captions always when mouse is over CC. --- .../xmodule/xmodule/css/video/display.scss | 4 ++-- .../xmodule/js/src/video/09_video_caption.js | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index c087d18098..36dbc76353 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -392,7 +392,7 @@ div.video { @include transition(none); -webkit-font-smoothing: antialiased; width: 30px; - + &:hover, &:active { background-color: #444; color: #fff; @@ -457,7 +457,7 @@ div.video { text-indent: -9999px; @include transition(none); width: 30px; - + &:hover, &:active { background-color: #444; color: #fff; diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index b11f08ce21..f69bcc1600 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -170,15 +170,22 @@ function () { } else if (!this.config.autohideHtml5) { this.videoCaption.subtitlesEl.on({ keydown: this.videoCaption.autoShowCaptions, + focus: this.videoCaption.autoShowCaptions, // Moving slider on subtitles is not a mouse move, but captions // should not be auto-hidden. - scroll: this.videoCaption.autoShowCaptions + scroll: this.videoCaption.autoShowCaptions, + + mouseout: this.videoCaption.autoHideCaptions, + blur: this.videoCaption.autoHideCaptions }); this.videoCaption.hideSubtitlesEl.on({ mousemove: this.videoCaption.autoShowCaptions, - keydown: this.videoCaption.autoShowCaptions + focus: this.videoCaption.autoShowCaptions, + + mouseout: this.videoCaption.autoHideCaptions, + blur: this.videoCaption.autoHideCaptions }); } } @@ -272,10 +279,12 @@ function () { clearTimeout(this.captionHideTimeout); } - this.captionHideTimeout = setTimeout( - this.videoCaption.autoHideCaptions, - this.videoCaption.fadeOutTimeout - ); + if (this.config.autohideHtml5) { + this.captionHideTimeout = setTimeout( + this.videoCaption.autoHideCaptions, + this.videoCaption.fadeOutTimeout + ); + } this.captionsShowLock = false; } @@ -502,6 +511,8 @@ function () { // forward out of the captions. if (captionIndex === 0 || captionIndex === this.videoCaption.captions.length-1) { + this.videoCaption.autoHideCaptions(); + this.videoCaption.autoScrolling = true; } } From 4b371d4748de7d3a399c4cdeb24210f2bdce937c Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Tue, 8 Oct 2013 16:52:21 +0300 Subject: [PATCH 17/75] Fixing Jean Michel access. bug. --- common/lib/xmodule/xmodule/css/video/display.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index 36dbc76353..2b4620ebee 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -610,6 +610,8 @@ div.video { ol.subtitles { width: 0; height: 0; + + visibility: hidden; } ol.subtitles.html5 { @@ -643,6 +645,8 @@ div.video { ol.subtitles { right: -(flex-grid(4)); width: auto; + + visibility: hidden; } } From 8ec9a16d81dc89aeb47095adb9b51444e77cb789 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 2 Oct 2013 16:14:26 -0400 Subject: [PATCH 18/75] Fix parentheses so that we only pull the course modes we want. --- common/djangoapps/course_modes/models.py | 4 ++-- common/djangoapps/course_modes/tests/test_models.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index fa1a687e47..ce15059643 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -55,8 +55,8 @@ class CourseMode(models.Model): """ now = datetime.now(pytz.UTC) found_course_modes = cls.objects.filter(Q(course_id=course_id) & - Q(expiration_date__isnull=True) | - Q(expiration_date__gte=now)) + (Q(expiration_date__isnull=True) | + Q(expiration_date__gte=now))) modes = ([Mode(mode.mode_slug, mode.mode_display_name, mode.min_price, mode.suggested_prices, mode.currency) for mode in found_course_modes]) if not modes: diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py index deeed6ea9b..651c7c51a5 100644 --- a/common/djangoapps/course_modes/tests/test_models.py +++ b/common/djangoapps/course_modes/tests/test_models.py @@ -102,3 +102,12 @@ class CourseModeModelTest(TestCase): self.create_mode(mode1.slug, mode1.name, mode1.min_price, mode1.suggested_prices) modes = CourseMode.modes_for_course(self.course_id) self.assertEqual([mode1], modes) + + expired_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=1) + expired_mode.save() + expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd') + modes = CourseMode.modes_for_course(self.course_id) + self.assertEqual([expired_mode_value, mode1], modes) + + modes = CourseMode.modes_for_course('second_test_course') + self.assertEqual([CourseMode.DEFAULT_MODE], modes) From 3cfaa6de206ba00689327801012b09fddbd6b764 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 3 Oct 2013 08:30:32 -0400 Subject: [PATCH 19/75] Add changelog --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bd28c47a74..23814b0454 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. + +LMS: Fix issue with CourseMode expiration dates + LMS: Add PaidCourseRegistration mode, where payment is required before course registration. LMS: Add split testing functionality for internal use. From de48e4a9d693f2f64f52a19d9be6a8a8afd21fa9 Mon Sep 17 00:00:00 2001 From: polesye Date: Thu, 10 Oct 2013 17:15:57 +0300 Subject: [PATCH 20/75] Add EmptyDataRawDescriptor. --- common/lib/xmodule/xmodule/lti_module.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py index e2c4832251..cd330ab9a2 100644 --- a/common/lib/xmodule/xmodule/lti_module.py +++ b/common/lib/xmodule/xmodule/lti_module.py @@ -10,6 +10,7 @@ import oauthlib.oauth1 import urllib from xmodule.editing_module import MetadataOnlyEditingDescriptor +from xmodule.raw_module import EmptyDataRawDescriptor from xmodule.x_module import XModule from xmodule.course_module import CourseDescriptor from pkg_resources import resource_string @@ -254,8 +255,8 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'} return params -class LTIModuleDescriptor(LTIFields, MetadataOnlyEditingDescriptor): +class LTIModuleDescriptor(LTIFields, MetadataOnlyEditingDescriptor, EmptyDataRawDescriptor): """ - LTIModuleDescriptor provides no export/import to xml. + Descriptor for LTI Xmodule. """ module_class = LTIModule From ef2218296f767d7228591d6987e45284a65d0a59 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 2 Oct 2013 16:51:04 -0400 Subject: [PATCH 21/75] LMS: revises CSS reference/pipeline architecture to handle IE9 selector limits --- .gitignore | 2 + lms/envs/common.py | 46 +- lms/static/sass/application-extend1.scss.mako | 62 + lms/static/sass/application-extend2.scss.mako | 54 + lms/static/sass/application.scss.mako | 38 +- lms/static/sass/elements/_typography.scss | 89 ++ lms/static/sass/ie.scss | 300 ++-- lms/static/sass/vendor/_font-awesome.scss | 1268 ----------------- lms/static/sass/views/_verification.scss | 90 +- .../courseware/courseware-error.html | 3 +- lms/templates/courseware/courseware.html | 7 +- lms/templates/courseware/gradebook.html | 3 +- lms/templates/courseware/info.html | 3 +- .../courseware/instructor_dashboard.html | 4 +- lms/templates/courseware/news.html | 3 +- lms/templates/courseware/progress.html | 3 +- lms/templates/courseware/static_tab.html | 3 +- lms/templates/courseware/syllabus.html | 3 +- lms/templates/discussion/index.html | 8 +- lms/templates/discussion/single_thread.html | 4 +- lms/templates/discussion/user_profile.html | 4 +- lms/templates/instructor/hint_manager.html | 28 +- .../instructor_dashboard_2.html | 3 +- lms/templates/instructor/staff_grading.html | 3 +- lms/templates/main.html | 16 +- lms/templates/mktg_iframe.html | 4 +- lms/templates/notes.html | 6 +- .../combined_notifications.html | 3 +- .../open_ended_flagged_problems.html | 3 +- .../open_ended_problems.html | 3 +- lms/templates/static_htmlbook.html | 3 +- lms/templates/static_pdfbook.html | 3 +- lms/templates/staticbook.html | 4 +- lms/templates/stripped-main.html | 3 +- 34 files changed, 483 insertions(+), 1596 deletions(-) create mode 100644 lms/static/sass/application-extend1.scss.mako create mode 100644 lms/static/sass/application-extend2.scss.mako delete mode 100644 lms/static/sass/vendor/_font-awesome.scss diff --git a/.gitignore b/.gitignore index e92d49a0f2..4df5d79916 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,8 @@ node_modules *.scssc lms/static/sass/*.css lms/static/sass/application.scss +lms/static/sass/application-extend1.scss +lms/static/sass/application-extend2.scss lms/static/sass/course.scss cms/static/sass/*.css diff --git a/lms/envs/common.py b/lms/envs/common.py index 96b304294d..72e4a118e1 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -587,7 +587,7 @@ MIDDLEWARE_CLASSES = ( # catches any uncaught RateLimitExceptions and returns a 403 instead of a 500 'ratelimitbackend.middleware.RateLimitMiddleware', - + # For A/B testing 'waffle.middleware.WaffleMiddleware', ) @@ -628,25 +628,49 @@ open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_end notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.coffee')) PIPELINE_CSS = { - 'application': { - 'source_filenames': ['sass/application.css'], - 'output_filename': 'css/lms-application.css', + 'style-vendor': { + 'source_filenames': [ + 'css/vendor/font-awesome.css', + 'css/vendor/jquery.qtip.min.css', + 'css/vendor/responsive-carousel/responsive-carousel', + 'css/vendor/responsive-carousel/responsive-carousel.slide', + ], + 'output_filename': 'css/lms-style-vendor.css', }, - 'course': { + 'style-app': { + 'source_filenames': [ + 'sass/application.css', + 'sass/ie.css' + ], + 'output_filename': 'css/lms-style-app.css', + }, + 'style-app-extend1': { + 'source_filenames': [ + 'sass/application-extend1.css', + ], + 'output_filename': 'css/lms-style-app-extend1.css', + }, + 'style-app-extend2': { + 'source_filenames': [ + 'sass/application-extend2.css', + ], + 'output_filename': 'css/lms-style-app-extend2.css', + }, + 'style-course-vendor': { 'source_filenames': [ 'js/vendor/CodeMirror/codemirror.css', 'css/vendor/jquery.treeview.css', 'css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css', - 'css/vendor/jquery.qtip.min.css', 'css/vendor/annotator.min.css', + ], + 'output_filename': 'css/lms-style-course-vendor.css', + }, + 'style-course': { + 'source_filenames': [ 'sass/course.css', 'xmodule/modules.css', ], - 'output_filename': 'css/lms-course.css', - }, - 'ie-fixes': { - 'source_filenames': ['sass/ie.css'], - 'output_filename': 'css/lms-ie.css', + 'output_filename': 'css/lms-style-course.css', }, } diff --git a/lms/static/sass/application-extend1.scss.mako b/lms/static/sass/application-extend1.scss.mako new file mode 100644 index 0000000000..86a442301d --- /dev/null +++ b/lms/static/sass/application-extend1.scss.mako @@ -0,0 +1,62 @@ +## NOTE: This Sass infrastructure is redundant, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx + + +// lms - css application architecture (platform) +// ==================== + +// libs and resets *do not edit* +@import 'bourbon/bourbon'; // lib - bourbon + +// BASE *default edX offerings* +// ==================== + +// base - utilities +@import 'base/reset'; +@import 'base/mixins'; +@import 'base/variables'; + +## THEMING +## ------- +## Set up this file to import an edX theme library if the environment +## indicates that a theme should be used. The assumption is that the +## theme resides outside of this main edX repository, in a directory +## called themes//, with its base Sass file in +## themes//static/sass/_.scss. That one entry +## point can be used to @import in as many other things as needed. +% if env.get('THEME_NAME') is not None: + // import theme's Sass overrides + @import '${env.get('THEME_NAME')}'; +% endif + +@import 'base/base'; + +// base - assets +@import 'base/font_face'; +@import 'base/extends'; +@import 'base/animations'; + +// base - starter +@import 'base/base'; + +// base - elements +@import 'elements/typography'; +@import 'elements/controls'; + +// shared - platform +@import 'multicourse/home'; +@import 'multicourse/dashboard'; +@import 'multicourse/account'; +@import 'multicourse/testcenter-register'; +@import 'multicourse/courses'; +@import 'multicourse/course_about'; +@import 'multicourse/jobs'; +@import 'multicourse/media-kit'; +@import 'multicourse/about_pages'; +@import 'multicourse/press_release'; +@import 'multicourse/password_reset'; +@import 'multicourse/error-pages'; +@import 'multicourse/help'; +@import 'multicourse/edge'; + +## NOTE: needed here for cascade and dependency purposes, but not a great permanent solution +@import 'shame'; // shame file - used for any bad-form/orphaned scss that knowingly violate edX FED architecture/standards (see - http://csswizardry.com/2013/04/shame-css/) diff --git a/lms/static/sass/application-extend2.scss.mako b/lms/static/sass/application-extend2.scss.mako new file mode 100644 index 0000000000..32c32e9d2f --- /dev/null +++ b/lms/static/sass/application-extend2.scss.mako @@ -0,0 +1,54 @@ +## NOTE: This Sass infrastructure is redundant, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx + + +// lms - css application architecture (platform) +// ==================== + +// libs and resets *do not edit* +@import 'bourbon/bourbon'; // lib - bourbon + +// BASE *default edX offerings* +// ==================== + +// base - utilities +@import 'base/reset'; +@import 'base/mixins'; +@import 'base/variables'; + +## THEMING +## ------- +## Set up this file to import an edX theme library if the environment +## indicates that a theme should be used. The assumption is that the +## theme resides outside of this main edX repository, in a directory +## called themes//, with its base Sass file in +## themes//static/sass/_.scss. That one entry +## point can be used to @import in as many other things as needed. +% if env.get('THEME_NAME') is not None: + // import theme's Sass overrides + @import '${env.get('THEME_NAME')}'; +% endif + +@import 'base/base'; + +// base - assets +@import 'base/font_face'; +@import 'base/extends'; +@import 'base/animations'; + +// base - starter +@import 'base/base'; + +// base - elements +@import 'elements/typography'; +@import 'elements/controls'; + +// base - specific views +@import 'views/verification'; +@import 'views/shoppingcart'; + +// applications +@import 'discussion'; +@import 'news'; + +## NOTE: needed here for cascade and dependency purposes, but not a great permanent solution +@import 'shame'; // shame file - used for any bad-form/orphaned scss that knowingly violate edX FED architecture/standards (see - http://csswizardry.com/2013/04/shame-css/) diff --git a/lms/static/sass/application.scss.mako b/lms/static/sass/application.scss.mako index a500748121..16688d5368 100644 --- a/lms/static/sass/application.scss.mako +++ b/lms/static/sass/application.scss.mako @@ -1,21 +1,16 @@ +## Note: This Sass infrastructure is repeated in application-extend1 and application-extend2, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx + // lms - css application architecture // ==================== // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon -// VENDOR + REBASE *referenced/used vendor presentation and reset* -// ==================== - -@import 'base/reset'; -@import 'vendor/font-awesome'; -@import 'vendor/responsive-carousel/responsive-carousel'; -@import 'vendor/responsive-carousel/responsive-carousel.slide'; - // BASE *default edX offerings* // ==================== -// base - utilities +// base - utilities +@import 'base/reset'; @import 'base/mixins'; @import 'base/variables'; @@ -46,10 +41,6 @@ @import 'elements/typography'; @import 'elements/controls'; -// base - specific views -@import 'views/verification'; -@import 'views/shoppingcart'; - // shared - course @import 'shared/forms'; @import 'shared/footer'; @@ -60,24 +51,5 @@ @import 'shared/activation_messages'; @import 'shared/unsubscribe'; -// shared - platform -@import 'multicourse/home'; -@import 'multicourse/dashboard'; -@import 'multicourse/account'; -@import 'multicourse/testcenter-register'; -@import 'multicourse/courses'; -@import 'multicourse/course_about'; -@import 'multicourse/jobs'; -@import 'multicourse/media-kit'; -@import 'multicourse/about_pages'; -@import 'multicourse/press_release'; -@import 'multicourse/password_reset'; -@import 'multicourse/error-pages'; -@import 'multicourse/help'; -@import 'multicourse/edge'; - -// applications -@import 'discussion'; -@import 'news'; - +## NOTE: needed here for cascade and dependency purposes, but not a great permanent solution @import 'shame'; // shame file - used for any bad-form/orphaned scss that knowingly violate edX FED architecture/standards (see - http://csswizardry.com/2013/04/shame-css/) diff --git a/lms/static/sass/elements/_typography.scss b/lms/static/sass/elements/_typography.scss index 043a205278..2cf11ce79b 100644 --- a/lms/static/sass/elements/_typography.scss +++ b/lms/static/sass/elements/_typography.scss @@ -194,3 +194,92 @@ %t-weight5 { font-weight: 700; } + +// ==================== + +// MISC: extends - type +// application: canned headings +%hd-lv1 { + @extend %t-title1; + @extend %t-weight1; + color: $m-gray-d4; + margin: 0 0 ($baseline*2) 0; +} + +%hd-lv2 { + @extend %t-title4; + @extend %t-weight1; + margin: 0 0 ($baseline*0.75) 0; + border-bottom: 1px solid $m-gray-l3; + padding-bottom: ($baseline/2); + color: $m-gray-d4; +} + +%hd-lv3 { + @extend %t-title6; + @extend %t-weight4; + margin: 0 0 ($baseline/4) 0; + color: $m-gray-d4; +} + +%hd-lv4 { + @extend %t-title6; + @extend %t-weight2; + margin: 0 0 $baseline 0; + color: $m-gray-d4; +} + +%hd-lv5 { + @extend %t-title7; + @extend %t-weight4; + margin: 0 0 ($baseline/4) 0; + color: $m-gray-d4; +} + +// application: canned copy +%copy-base { + @extend %t-copy-base; + color: $m-gray-d2; +} + +%copy-lead1 { + @extend %t-copy-lead2; + color: $m-gray; +} + +%copy-detail { + @extend %t-copy-sub1; + @extend %t-weight3; + color: $m-gray-d1; +} + +%copy-metadata { + @extend %t-copy-sub2; + color: $m-gray-d1; + + + %copy-metadata-value { + @extend %t-weight2; + } + + %copy-metadata-value { + @extend %t-weight4; + } +} + +// application: canned links +%copy-link { + border-bottom: 1px dotted transparent; + + &:hover, &:active { + border-color: $link-color-d1; + } +} + +%copy-badge { + @extend %t-title8; + @extend %t-weight5; + border-radius: ($baseline/5); + padding: ($baseline/2) $baseline; + text-transform: uppercase; +} diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index 87a8b07fe6..9ba7e612c1 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -2,184 +2,188 @@ @import "base/variables"; // These are all quick solutions for IE please rewrite -//Make overlay white because ie doesn't like rgba -.highlighted-courses .courses .course header.course-preview, .find-courses .courses .course header.course-preview, -.home .highlighted-courses > h2, .home .highlighted-courses > section.outside-app h1, section.outside-app .home .highlighted-courses > h1, -header.global { - background: #FFF; -} +.ie { -// hide all actions -.home > header .title .actions, -.home > header .title:hover .actions { - display: none; - height: auto; -} + //Make overlay white because ie doesn't like rgba + .highlighted-courses .courses .course header.course-preview, .find-courses .courses .course header.course-preview, + .home .highlighted-courses > h2, .home .highlighted-courses > section.outside-app h1, section.outside-app .home .highlighted-courses > h1, + header.global { + background: #FFF; + } -.home > header .title { - &:hover { + // hide all actions + .home > header .title .actions, + .home > header .title:hover .actions { + display: none; + height: auto; + } - > hgroup { - h1 { - border-bottom: 0; - padding-bottom: 0; + .home > header .title { + &:hover { + + > hgroup { + h1 { + border-bottom: 0; + padding-bottom: 0; + } + + h2 { + opacity: 1.0; + } } - h2 { + .actions { + opacity: 0; + } + } + } + + // because ie doesn't like :last + .last { + margin-right: 0 !important; + } + + // make partners not animate + .home .university-partners .partners a { + .name { + position: static; + } + + &:hover { + text-decoration: none; + + &::before { opacity: 1.0; } + + .name { + bottom: 0px; + } + + img { + top: 0px; + } } - .actions { - opacity: 0; - } - } -} - -// because ie doesn't like :last -.last { - margin-right: 0 !important; -} - -// make partners not animate -.home .university-partners .partners a { - .name { - position: static; } - &:hover { - text-decoration: none; + .home .university-partners .partners { + width: 660px; - &::before { - opacity: 1.0; + li.partner { + float: left; + display: block; + padding: 0; + width: 220px; + overflow: hidden; + } + } + + // make animations on homepage not animate and show everything + .highlighted-courses .courses .course, .find-courses .courses .course { + .meta-info { + display: none; } - .name { - bottom: 0px; + .inner-wrapper { + height: 100%; + overflow: visible; + position: relative; } - img { + header.course-preview { + left: 0px; + position: relative; top: 0px; + width: 100%; + z-index: 3; + height: auto; + + hgroup { + position: relative; + right: 0; + top: 0; + } + + } + + .info { + height: auto; + position: static; + overflow: visible; + + .desc { + height: auto; + } + } + + &:hover { + background: rgb(245,245,245); + border-color: rgb(170,170,170); + box-shadow: 0 1px 16px 0 rgba($blue, 0.4); + + .info { + top: 0; + } + + .meta-info { + opacity: 0; + } } } -} - -.home .university-partners .partners { - width: 660px; - - li.partner { - float: left; - display: block; - padding: 0; - width: 220px; - overflow: hidden; + // make overlay flat black since IE cant handle rgba + #lean_overlay { + background: #000; } -} -// make animations on homepage not animate and show everything -.highlighted-courses .courses .course, .find-courses .courses .course { - .meta-info { + // active navigation + nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { + background-color: #333; + background-color: rgba(0, 0, 0, .4); + } + + // make dropdown user consistent size + header.global ol.user > li.primary a.dropdown { + padding-top: 6px; + padding-bottom: 6px; + } + + // always hide arrow in IE + .dashboard .my-courses .my-course .cover .arrow { display: none; } - .inner-wrapper { - height: 100%; - overflow: visible; - position: relative; - } - - header.course-preview { - left: 0px; - position: relative; - top: 0px; - width: 100%; - z-index: 3; - height: auto; - - hgroup { - position: relative; - right: 0; - top: 0; - } - - } - - .info { - height: auto; - position: static; - overflow: visible; - - .desc { - height: auto; - } - } - - &:hover { - background: rgb(245,245,245); - border-color: rgb(170,170,170); - box-shadow: 0 1px 16px 0 rgba($blue, 0.4); - - .info { - top: 0; - } - - .meta-info { - opacity: 0; - } - } -} - -// make overlay flat black since IE cant handle rgba -#lean_overlay { - background: #000; -} - -// active navigation -nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { - background-color: #333; - background-color: rgba(0, 0, 0, .4); -} - -// make dropdown user consistent size -header.global ol.user > li.primary a.dropdown { - padding-top: 6px; - padding-bottom: 6px; -} - -// always hide arrow in IE -.dashboard .my-courses .my-course .cover .arrow { - display: none; -} - -.ie-banner { - display: block !important; -} - -div.course-wrapper { - display: block !important; - - section.course-content, - section.course-index { + .ie-banner { display: block !important; - float: left; } - section.course-content { - width: 71.27%; + div.course-wrapper { + display: block !important; + + section.course-content, + section.course-index { + display: block !important; + float: left; + } + + section.course-content { + width: 71.27%; + } } -} -.sidebar { - float: left !important; - display: block !important; -} - -.sequence-nav ol { - display: block !important; - - li { + .sidebar { float: left !important; - width: 50px; + display: block !important; } + + .sequence-nav ol { + display: block !important; + + li { + float: left !important; + width: 50px; + } + } + } diff --git a/lms/static/sass/vendor/_font-awesome.scss b/lms/static/sass/vendor/_font-awesome.scss deleted file mode 100644 index 035ca8c750..0000000000 --- a/lms/static/sass/vendor/_font-awesome.scss +++ /dev/null @@ -1,1268 +0,0 @@ -/*! - * Font Awesome 3.1.0 - * the iconic font designed for Bootstrap - * ------------------------------------------------------- - * The full suite of pictographic icons, examples, and documentation - * can be found at: http://fontawesome.io - * - * License - * ------------------------------------------------------- - * - The Font Awesome font is licensed under the SIL Open Font License v1.1 - - * http://scripts.sil.org/OFL - * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - - * http://opensource.org/licenses/mit-license.html - * - Font Awesome documentation licensed under CC BY 3.0 License - - * http://creativecommons.org/licenses/by/3.0/ - * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: - * "Font Awesome by Dave Gandy - http://fontawesome.io" - - * Contact - * ------------------------------------------------------- - * Email: dave@fontawesome.io - * Twitter: http://twitter.com/fortaweso_me - * Work: Lead Product Designer @ http://kyruus.com - */ -/* FONT PATH - * -------------------------- */ -@font-face { - font-family: 'FontAwesome'; - src: url('../fonts/vendor/fontawesome-webfont.eot?v=3.1.0'); - src: url('../fonts/vendor/fontawesome-webfont.eot?#iefix&v=3.1.0') format('embedded-opentype'), url('../fonts/vendor/fontawesome-webfont.woff?v=3.1.0') format('woff'), url('../fonts/vendor/fontawesome-webfont.ttf?v=3.1.0') format('truetype'), url('../fonts/vendor/fontawesome-webfont.svg#fontawesomeregular?v=3.1.0') format('svg'); - font-weight: normal; - font-style: normal; -} -/* FONT AWESOME CORE - * -------------------------- */ -[class^="icon-"], -[class*=" icon-"] { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; -} -[class^="icon-"]:before, -[class*=" icon-"]:before { - text-decoration: inherit; - display: inline-block; - speak: none; -} -/* makes the font 33% larger relative to the icon container */ -.icon-large:before { - vertical-align: -10%; - font-size: 1.3333333333333333em; -} -/* makes sure icons active on rollover in links */ -a [class^="icon-"], -a [class*=" icon-"], -a [class^="icon-"]:before, -a [class*=" icon-"]:before { - display: inline; -} -/* increased font size for icon-large */ -[class^="icon-"].icon-fixed-width, -[class*=" icon-"].icon-fixed-width { - display: inline-block; - width: 1.2857142857142858em; - text-align: center; -} -[class^="icon-"].icon-fixed-width.icon-large, -[class*=" icon-"].icon-fixed-width.icon-large { - width: 1.5714285714285714em; -} -ul.icons-ul { - list-style-type: none; - text-indent: -0.7142857142857143em; - margin-left: 2.142857142857143em; -} -ul.icons-ul > li .icon-li { - width: 0.7142857142857143em; - display: inline-block; - text-align: center; -} -[class^="icon-"].hide, -[class*=" icon-"].hide { - display: none; -} -.icon-muted { - color: #eeeeee; -} -.icon-light { - color: #ffffff; -} -.icon-dark { - color: #333333; -} -.icon-border { - border: solid 1px #eeeeee; - padding: .2em .25em .15em; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -.icon-2x { - font-size: 2em; -} -.icon-2x.icon-border { - border-width: 2px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.icon-3x { - font-size: 3em; -} -.icon-3x.icon-border { - border-width: 3px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} -.icon-4x { - font-size: 4em; -} -.icon-4x.icon-border { - border-width: 4px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} -.icon-5x { - font-size: 5em; -} -.icon-5x.icon-border { - border-width: 5px; - -webkit-border-radius: 7px; - -moz-border-radius: 7px; - border-radius: 7px; -} -.pull-right { - float: right; -} -.pull-left { - float: left; -} -[class^="icon-"].pull-left, -[class*=" icon-"].pull-left { - margin-right: .3em; -} -[class^="icon-"].pull-right, -[class*=" icon-"].pull-right { - margin-left: .3em; -} -/* BOOTSTRAP SPECIFIC CLASSES - * -------------------------- */ -/* Bootstrap 2.0 sprites.less reset */ -[class^="icon-"], -[class*=" icon-"] { - display: inline; - width: auto; - height: auto; - line-height: normal; - vertical-align: baseline; - background-image: none; - background-position: 0% 0%; - background-repeat: repeat; - margin-top: 0; -} -/* more sprites.less reset */ -.icon-white, -.nav-pills > .active > a > [class^="icon-"], -.nav-pills > .active > a > [class*=" icon-"], -.nav-list > .active > a > [class^="icon-"], -.nav-list > .active > a > [class*=" icon-"], -.navbar-inverse .nav > .active > a > [class^="icon-"], -.navbar-inverse .nav > .active > a > [class*=" icon-"], -.dropdown-menu > li > a:hover > [class^="icon-"], -.dropdown-menu > li > a:hover > [class*=" icon-"], -.dropdown-menu > .active > a > [class^="icon-"], -.dropdown-menu > .active > a > [class*=" icon-"], -.dropdown-submenu:hover > a > [class^="icon-"], -.dropdown-submenu:hover > a > [class*=" icon-"] { - background-image: none; -} -/* keeps Bootstrap styles with and without icons the same */ -.btn [class^="icon-"].icon-large, -.nav [class^="icon-"].icon-large, -.btn [class*=" icon-"].icon-large, -.nav [class*=" icon-"].icon-large { - line-height: .9em; -} -.btn [class^="icon-"].icon-spin, -.nav [class^="icon-"].icon-spin, -.btn [class*=" icon-"].icon-spin, -.nav [class*=" icon-"].icon-spin { - display: inline-block; -} -.nav-tabs [class^="icon-"], -.nav-pills [class^="icon-"], -.nav-tabs [class*=" icon-"], -.nav-pills [class*=" icon-"], -.nav-tabs [class^="icon-"].icon-large, -.nav-pills [class^="icon-"].icon-large, -.nav-tabs [class*=" icon-"].icon-large, -.nav-pills [class*=" icon-"].icon-large { - line-height: .9em; -} -.btn [class^="icon-"].pull-left.icon-2x, -.btn [class*=" icon-"].pull-left.icon-2x, -.btn [class^="icon-"].pull-right.icon-2x, -.btn [class*=" icon-"].pull-right.icon-2x { - margin-top: .18em; -} -.btn [class^="icon-"].icon-spin.icon-large, -.btn [class*=" icon-"].icon-spin.icon-large { - line-height: .8em; -} -.btn.btn-small [class^="icon-"].pull-left.icon-2x, -.btn.btn-small [class*=" icon-"].pull-left.icon-2x, -.btn.btn-small [class^="icon-"].pull-right.icon-2x, -.btn.btn-small [class*=" icon-"].pull-right.icon-2x { - margin-top: .25em; -} -.btn.btn-large [class^="icon-"], -.btn.btn-large [class*=" icon-"] { - margin-top: 0; -} -.btn.btn-large [class^="icon-"].pull-left.icon-2x, -.btn.btn-large [class*=" icon-"].pull-left.icon-2x, -.btn.btn-large [class^="icon-"].pull-right.icon-2x, -.btn.btn-large [class*=" icon-"].pull-right.icon-2x { - margin-top: .05em; -} -.btn.btn-large [class^="icon-"].pull-left.icon-2x, -.btn.btn-large [class*=" icon-"].pull-left.icon-2x { - margin-right: .2em; -} -.btn.btn-large [class^="icon-"].pull-right.icon-2x, -.btn.btn-large [class*=" icon-"].pull-right.icon-2x { - margin-left: .2em; -} -/* EXTRAS - * -------------------------- */ -/* Stacked and layered icon */ -.icon-stack { - position: relative; - display: inline-block; - width: 2em; - height: 2em; - line-height: 2em; - vertical-align: -35%; -} -.icon-stack [class^="icon-"], -.icon-stack [class*=" icon-"] { - display: block; - text-align: center; - position: absolute; - width: 100%; - height: 100%; - font-size: 1em; - line-height: inherit; - *line-height: 2em; -} -.icon-stack .icon-stack-base { - font-size: 2em; - *line-height: 1em; -} -/* Animated rotating icon */ -.icon-spin { - display: inline-block; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -o-transform: rotate(0deg); - } - 100% { - -o-transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -ms-transform: rotate(0deg); - } - 100% { - -ms-transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(359deg); - } -} -/* Icon rotations and mirroring */ -.icon-rotate-90:before { - -webkit-transform: rotate(90deg); - -moz-transform: rotate(90deg); - -ms-transform: rotate(90deg); - -o-transform: rotate(90deg); - transform: rotate(90deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); -} -.icon-rotate-180:before { - -webkit-transform: rotate(180deg); - -moz-transform: rotate(180deg); - -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); - transform: rotate(180deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); -} -.icon-rotate-270:before { - -webkit-transform: rotate(270deg); - -moz-transform: rotate(270deg); - -ms-transform: rotate(270deg); - -o-transform: rotate(270deg); - transform: rotate(270deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); -} -.icon-flip-horizontal:before { - -webkit-transform: scale(-1, 1); - -moz-transform: scale(-1, 1); - -ms-transform: scale(-1, 1); - -o-transform: scale(-1, 1); - transform: scale(-1, 1); -} -.icon-flip-vertical:before { - -webkit-transform: scale(1, -1); - -moz-transform: scale(1, -1); - -ms-transform: scale(1, -1); - -o-transform: scale(1, -1); - transform: scale(1, -1); -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.icon-glass:before { - content: "\f000"; -} -.icon-music:before { - content: "\f001"; -} -.icon-search:before { - content: "\f002"; -} -.icon-envelope:before { - content: "\f003"; -} -.icon-heart:before { - content: "\f004"; -} -.icon-star:before { - content: "\f005"; -} -.icon-star-empty:before { - content: "\f006"; -} -.icon-user:before { - content: "\f007"; -} -.icon-film:before { - content: "\f008"; -} -.icon-th-large:before { - content: "\f009"; -} -.icon-th:before { - content: "\f00a"; -} -.icon-th-list:before { - content: "\f00b"; -} -.icon-ok:before { - content: "\f00c"; -} -.icon-remove:before { - content: "\f00d"; -} -.icon-zoom-in:before { - content: "\f00e"; -} -.icon-zoom-out:before { - content: "\f010"; -} -.icon-off:before { - content: "\f011"; -} -.icon-signal:before { - content: "\f012"; -} -.icon-cog:before { - content: "\f013"; -} -.icon-trash:before { - content: "\f014"; -} -.icon-home:before { - content: "\f015"; -} -.icon-file:before { - content: "\f016"; -} -.icon-time:before { - content: "\f017"; -} -.icon-road:before { - content: "\f018"; -} -.icon-download-alt:before { - content: "\f019"; -} -.icon-download:before { - content: "\f01a"; -} -.icon-upload:before { - content: "\f01b"; -} -.icon-inbox:before { - content: "\f01c"; -} -.icon-play-circle:before { - content: "\f01d"; -} -.icon-repeat:before, -.icon-rotate-right:before { - content: "\f01e"; -} -/* F020 doesn't work in Safari. all shifted one down */ -.icon-refresh:before { - content: "\f021"; -} -.icon-list-alt:before { - content: "\f022"; -} -.icon-lock:before { - content: "\f023"; -} -.icon-flag:before { - content: "\f024"; -} -.icon-headphones:before { - content: "\f025"; -} -.icon-volume-off:before { - content: "\f026"; -} -.icon-volume-down:before { - content: "\f027"; -} -.icon-volume-up:before { - content: "\f028"; -} -.icon-qrcode:before { - content: "\f029"; -} -.icon-barcode:before { - content: "\f02a"; -} -.icon-tag:before { - content: "\f02b"; -} -.icon-tags:before { - content: "\f02c"; -} -.icon-book:before { - content: "\f02d"; -} -.icon-bookmark:before { - content: "\f02e"; -} -.icon-print:before { - content: "\f02f"; -} -.icon-camera:before { - content: "\f030"; -} -.icon-font:before { - content: "\f031"; -} -.icon-bold:before { - content: "\f032"; -} -.icon-italic:before { - content: "\f033"; -} -.icon-text-height:before { - content: "\f034"; -} -.icon-text-width:before { - content: "\f035"; -} -.icon-align-left:before { - content: "\f036"; -} -.icon-align-center:before { - content: "\f037"; -} -.icon-align-right:before { - content: "\f038"; -} -.icon-align-justify:before { - content: "\f039"; -} -.icon-list:before { - content: "\f03a"; -} -.icon-indent-left:before { - content: "\f03b"; -} -.icon-indent-right:before { - content: "\f03c"; -} -.icon-facetime-video:before { - content: "\f03d"; -} -.icon-picture:before { - content: "\f03e"; -} -.icon-pencil:before { - content: "\f040"; -} -.icon-map-marker:before { - content: "\f041"; -} -.icon-adjust:before { - content: "\f042"; -} -.icon-tint:before { - content: "\f043"; -} -.icon-edit:before { - content: "\f044"; -} -.icon-share:before { - content: "\f045"; -} -.icon-check:before { - content: "\f046"; -} -.icon-move:before { - content: "\f047"; -} -.icon-step-backward:before { - content: "\f048"; -} -.icon-fast-backward:before { - content: "\f049"; -} -.icon-backward:before { - content: "\f04a"; -} -.icon-play:before { - content: "\f04b"; -} -.icon-pause:before { - content: "\f04c"; -} -.icon-stop:before { - content: "\f04d"; -} -.icon-forward:before { - content: "\f04e"; -} -.icon-fast-forward:before { - content: "\f050"; -} -.icon-step-forward:before { - content: "\f051"; -} -.icon-eject:before { - content: "\f052"; -} -.icon-chevron-left:before { - content: "\f053"; -} -.icon-chevron-right:before { - content: "\f054"; -} -.icon-plus-sign:before { - content: "\f055"; -} -.icon-minus-sign:before { - content: "\f056"; -} -.icon-remove-sign:before { - content: "\f057"; -} -.icon-ok-sign:before { - content: "\f058"; -} -.icon-question-sign:before { - content: "\f059"; -} -.icon-info-sign:before { - content: "\f05a"; -} -.icon-screenshot:before { - content: "\f05b"; -} -.icon-remove-circle:before { - content: "\f05c"; -} -.icon-ok-circle:before { - content: "\f05d"; -} -.icon-ban-circle:before { - content: "\f05e"; -} -.icon-arrow-left:before { - content: "\f060"; -} -.icon-arrow-right:before { - content: "\f061"; -} -.icon-arrow-up:before { - content: "\f062"; -} -.icon-arrow-down:before { - content: "\f063"; -} -.icon-share-alt:before, -.icon-mail-forward:before { - content: "\f064"; -} -.icon-resize-full:before { - content: "\f065"; -} -.icon-resize-small:before { - content: "\f066"; -} -.icon-plus:before { - content: "\f067"; -} -.icon-minus:before { - content: "\f068"; -} -.icon-asterisk:before { - content: "\f069"; -} -.icon-exclamation-sign:before { - content: "\f06a"; -} -.icon-gift:before { - content: "\f06b"; -} -.icon-leaf:before { - content: "\f06c"; -} -.icon-fire:before { - content: "\f06d"; -} -.icon-eye-open:before { - content: "\f06e"; -} -.icon-eye-close:before { - content: "\f070"; -} -.icon-warning-sign:before { - content: "\f071"; -} -.icon-plane:before { - content: "\f072"; -} -.icon-calendar:before { - content: "\f073"; -} -.icon-random:before { - content: "\f074"; -} -.icon-comment:before { - content: "\f075"; -} -.icon-magnet:before { - content: "\f076"; -} -.icon-chevron-up:before { - content: "\f077"; -} -.icon-chevron-down:before { - content: "\f078"; -} -.icon-retweet:before { - content: "\f079"; -} -.icon-shopping-cart:before { - content: "\f07a"; -} -.icon-folder-close:before { - content: "\f07b"; -} -.icon-folder-open:before { - content: "\f07c"; -} -.icon-resize-vertical:before { - content: "\f07d"; -} -.icon-resize-horizontal:before { - content: "\f07e"; -} -.icon-bar-chart:before { - content: "\f080"; -} -.icon-twitter-sign:before { - content: "\f081"; -} -.icon-facebook-sign:before { - content: "\f082"; -} -.icon-camera-retro:before { - content: "\f083"; -} -.icon-key:before { - content: "\f084"; -} -.icon-cogs:before { - content: "\f085"; -} -.icon-comments:before { - content: "\f086"; -} -.icon-thumbs-up:before { - content: "\f087"; -} -.icon-thumbs-down:before { - content: "\f088"; -} -.icon-star-half:before { - content: "\f089"; -} -.icon-heart-empty:before { - content: "\f08a"; -} -.icon-signout:before { - content: "\f08b"; -} -.icon-linkedin-sign:before { - content: "\f08c"; -} -.icon-pushpin:before { - content: "\f08d"; -} -.icon-external-link:before { - content: "\f08e"; -} -.icon-signin:before { - content: "\f090"; -} -.icon-trophy:before { - content: "\f091"; -} -.icon-github-sign:before { - content: "\f092"; -} -.icon-upload-alt:before { - content: "\f093"; -} -.icon-lemon:before { - content: "\f094"; -} -.icon-phone:before { - content: "\f095"; -} -.icon-check-empty:before { - content: "\f096"; -} -.icon-bookmark-empty:before { - content: "\f097"; -} -.icon-phone-sign:before { - content: "\f098"; -} -.icon-twitter:before { - content: "\f099"; -} -.icon-facebook:before { - content: "\f09a"; -} -.icon-github:before { - content: "\f09b"; -} -.icon-unlock:before { - content: "\f09c"; -} -.icon-credit-card:before { - content: "\f09d"; -} -.icon-rss:before { - content: "\f09e"; -} -.icon-hdd:before { - content: "\f0a0"; -} -.icon-bullhorn:before { - content: "\f0a1"; -} -.icon-bell:before { - content: "\f0a2"; -} -.icon-certificate:before { - content: "\f0a3"; -} -.icon-hand-right:before { - content: "\f0a4"; -} -.icon-hand-left:before { - content: "\f0a5"; -} -.icon-hand-up:before { - content: "\f0a6"; -} -.icon-hand-down:before { - content: "\f0a7"; -} -.icon-circle-arrow-left:before { - content: "\f0a8"; -} -.icon-circle-arrow-right:before { - content: "\f0a9"; -} -.icon-circle-arrow-up:before { - content: "\f0aa"; -} -.icon-circle-arrow-down:before { - content: "\f0ab"; -} -.icon-globe:before { - content: "\f0ac"; -} -.icon-wrench:before { - content: "\f0ad"; -} -.icon-tasks:before { - content: "\f0ae"; -} -.icon-filter:before { - content: "\f0b0"; -} -.icon-briefcase:before { - content: "\f0b1"; -} -.icon-fullscreen:before { - content: "\f0b2"; -} -.icon-group:before { - content: "\f0c0"; -} -.icon-link:before { - content: "\f0c1"; -} -.icon-cloud:before { - content: "\f0c2"; -} -.icon-beaker:before { - content: "\f0c3"; -} -.icon-cut:before { - content: "\f0c4"; -} -.icon-copy:before { - content: "\f0c5"; -} -.icon-paper-clip:before { - content: "\f0c6"; -} -.icon-save:before { - content: "\f0c7"; -} -.icon-sign-blank:before { - content: "\f0c8"; -} -.icon-reorder:before { - content: "\f0c9"; -} -.icon-list-ul:before { - content: "\f0ca"; -} -.icon-list-ol:before { - content: "\f0cb"; -} -.icon-strikethrough:before { - content: "\f0cc"; -} -.icon-underline:before { - content: "\f0cd"; -} -.icon-table:before { - content: "\f0ce"; -} -.icon-magic:before { - content: "\f0d0"; -} -.icon-truck:before { - content: "\f0d1"; -} -.icon-pinterest:before { - content: "\f0d2"; -} -.icon-pinterest-sign:before { - content: "\f0d3"; -} -.icon-google-plus-sign:before { - content: "\f0d4"; -} -.icon-google-plus:before { - content: "\f0d5"; -} -.icon-money:before { - content: "\f0d6"; -} -.icon-caret-down:before { - content: "\f0d7"; -} -.icon-caret-up:before { - content: "\f0d8"; -} -.icon-caret-left:before { - content: "\f0d9"; -} -.icon-caret-right:before { - content: "\f0da"; -} -.icon-columns:before { - content: "\f0db"; -} -.icon-sort:before { - content: "\f0dc"; -} -.icon-sort-down:before { - content: "\f0dd"; -} -.icon-sort-up:before { - content: "\f0de"; -} -.icon-envelope-alt:before { - content: "\f0e0"; -} -.icon-linkedin:before { - content: "\f0e1"; -} -.icon-undo:before, -.icon-rotate-left:before { - content: "\f0e2"; -} -.icon-legal:before { - content: "\f0e3"; -} -.icon-dashboard:before { - content: "\f0e4"; -} -.icon-comment-alt:before { - content: "\f0e5"; -} -.icon-comments-alt:before { - content: "\f0e6"; -} -.icon-bolt:before { - content: "\f0e7"; -} -.icon-sitemap:before { - content: "\f0e8"; -} -.icon-umbrella:before { - content: "\f0e9"; -} -.icon-paste:before { - content: "\f0ea"; -} -.icon-lightbulb:before { - content: "\f0eb"; -} -.icon-exchange:before { - content: "\f0ec"; -} -.icon-cloud-download:before { - content: "\f0ed"; -} -.icon-cloud-upload:before { - content: "\f0ee"; -} -.icon-user-md:before { - content: "\f0f0"; -} -.icon-stethoscope:before { - content: "\f0f1"; -} -.icon-suitcase:before { - content: "\f0f2"; -} -.icon-bell-alt:before { - content: "\f0f3"; -} -.icon-coffee:before { - content: "\f0f4"; -} -.icon-food:before { - content: "\f0f5"; -} -.icon-file-alt:before { - content: "\f0f6"; -} -.icon-building:before { - content: "\f0f7"; -} -.icon-hospital:before { - content: "\f0f8"; -} -.icon-ambulance:before { - content: "\f0f9"; -} -.icon-medkit:before { - content: "\f0fa"; -} -.icon-fighter-jet:before { - content: "\f0fb"; -} -.icon-beer:before { - content: "\f0fc"; -} -.icon-h-sign:before { - content: "\f0fd"; -} -.icon-plus-sign-alt:before { - content: "\f0fe"; -} -.icon-double-angle-left:before { - content: "\f100"; -} -.icon-double-angle-right:before { - content: "\f101"; -} -.icon-double-angle-up:before { - content: "\f102"; -} -.icon-double-angle-down:before { - content: "\f103"; -} -.icon-angle-left:before { - content: "\f104"; -} -.icon-angle-right:before { - content: "\f105"; -} -.icon-angle-up:before { - content: "\f106"; -} -.icon-angle-down:before { - content: "\f107"; -} -.icon-desktop:before { - content: "\f108"; -} -.icon-laptop:before { - content: "\f109"; -} -.icon-tablet:before { - content: "\f10a"; -} -.icon-mobile-phone:before { - content: "\f10b"; -} -.icon-circle-blank:before { - content: "\f10c"; -} -.icon-quote-left:before { - content: "\f10d"; -} -.icon-quote-right:before { - content: "\f10e"; -} -.icon-spinner:before { - content: "\f110"; -} -.icon-circle:before { - content: "\f111"; -} -.icon-reply:before, -.icon-mail-reply:before { - content: "\f112"; -} -.icon-folder-close-alt:before { - content: "\f114"; -} -.icon-folder-open-alt:before { - content: "\f115"; -} -.icon-expand-alt:before { - content: "\f116"; -} -.icon-collapse-alt:before { - content: "\f117"; -} -.icon-smile:before { - content: "\f118"; -} -.icon-frown:before { - content: "\f119"; -} -.icon-meh:before { - content: "\f11a"; -} -.icon-gamepad:before { - content: "\f11b"; -} -.icon-keyboard:before { - content: "\f11c"; -} -.icon-flag-alt:before { - content: "\f11d"; -} -.icon-flag-checkered:before { - content: "\f11e"; -} -.icon-terminal:before { - content: "\f120"; -} -.icon-code:before { - content: "\f121"; -} -.icon-reply-all:before { - content: "\f122"; -} -.icon-mail-reply-all:before { - content: "\f122"; -} -.icon-star-half-full:before, -.icon-star-half-empty:before { - content: "\f123"; -} -.icon-location-arrow:before { - content: "\f124"; -} -.icon-crop:before { - content: "\f125"; -} -.icon-code-fork:before { - content: "\f126"; -} -.icon-unlink:before { - content: "\f127"; -} -.icon-question:before { - content: "\f128"; -} -.icon-info:before { - content: "\f129"; -} -.icon-exclamation:before { - content: "\f12a"; -} -.icon-superscript:before { - content: "\f12b"; -} -.icon-subscript:before { - content: "\f12c"; -} -.icon-eraser:before { - content: "\f12d"; -} -.icon-puzzle-piece:before { - content: "\f12e"; -} -.icon-microphone:before { - content: "\f130"; -} -.icon-microphone-off:before { - content: "\f131"; -} -.icon-shield:before { - content: "\f132"; -} -.icon-calendar-empty:before { - content: "\f133"; -} -.icon-fire-extinguisher:before { - content: "\f134"; -} -.icon-rocket:before { - content: "\f135"; -} -.icon-maxcdn:before { - content: "\f136"; -} -.icon-chevron-sign-left:before { - content: "\f137"; -} -.icon-chevron-sign-right:before { - content: "\f138"; -} -.icon-chevron-sign-up:before { - content: "\f139"; -} -.icon-chevron-sign-down:before { - content: "\f13a"; -} -.icon-html5:before { - content: "\f13b"; -} -.icon-css3:before { - content: "\f13c"; -} -.icon-anchor:before { - content: "\f13d"; -} -.icon-unlock-alt:before { - content: "\f13e"; -} -.icon-bullseye:before { - content: "\f140"; -} -.icon-ellipsis-horizontal:before { - content: "\f141"; -} -.icon-ellipsis-vertical:before { - content: "\f142"; -} -.icon-rss-sign:before { - content: "\f143"; -} -.icon-play-sign:before { - content: "\f144"; -} -.icon-ticket:before { - content: "\f145"; -} -.icon-minus-sign-alt:before { - content: "\f146"; -} -.icon-check-minus:before { - content: "\f147"; -} -.icon-level-up:before { - content: "\f148"; -} -.icon-level-down:before { - content: "\f149"; -} -.icon-check-sign:before { - content: "\f14a"; -} -.icon-edit-sign:before { - content: "\f14b"; -} -.icon-external-link-sign:before { - content: "\f14c"; -} -.icon-share-sign:before { - content: "\f14d"; -} diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index 4aa5f88e8b..8d0ea210ca 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -1,95 +1,7 @@ // lms - views - verification flow // ==================== -// MISC: extends - type -// application: canned headings -%hd-lv1 { - @extend %t-title1; - @extend %t-weight1; - color: $m-gray-d4; - margin: 0 0 ($baseline*2) 0; -} - -%hd-lv2 { - @extend %t-title4; - @extend %t-weight1; - margin: 0 0 ($baseline*0.75) 0; - border-bottom: 1px solid $m-gray-l3; - padding-bottom: ($baseline/2); - color: $m-gray-d4; -} - -%hd-lv3 { - @extend %t-title6; - @extend %t-weight4; - margin: 0 0 ($baseline/4) 0; - color: $m-gray-d4; -} - -%hd-lv4 { - @extend %t-title6; - @extend %t-weight2; - margin: 0 0 $baseline 0; - color: $m-gray-d4; -} - -%hd-lv5 { - @extend %t-title7; - @extend %t-weight4; - margin: 0 0 ($baseline/4) 0; - color: $m-gray-d4; -} - -// application: canned copy -%copy-base { - @extend %t-copy-base; - color: $m-gray-d2; -} - -%copy-lead1 { - @extend %t-copy-lead2; - color: $m-gray; -} - -%copy-detail { - @extend %t-copy-sub1; - @extend %t-weight3; - color: $m-gray-d1; -} - -%copy-metadata { - @extend %t-copy-sub2; - color: $m-gray-d1; - - - %copy-metadata-value { - @extend %t-weight2; - } - - %copy-metadata-value { - @extend %t-weight4; - } -} - -// application: canned links -%copy-link { - border-bottom: 1px dotted transparent; - - &:hover, &:active { - border-color: $link-color-d1; - } -} - -%copy-badge { - @extend %t-title8; - @extend %t-weight5; - border-radius: ($baseline/5); - padding: ($baseline/2) $baseline; - text-transform: uppercase; -} - -// ==================== - +// MISC: extends - button %btn-verify-primary { @extend %btn-primary-green; } diff --git a/lms/templates/courseware/courseware-error.html b/lms/templates/courseware/courseware-error.html index f0f7969026..9018495bff 100644 --- a/lms/templates/courseware/courseware-error.html +++ b/lms/templates/courseware/courseware-error.html @@ -6,7 +6,8 @@ <%block name="title">${_("Courseware")} - ${settings.PLATFORM_NAME} <%block name="headextra"> - <%static:css group='course'/> +<%static:css group='style-course-vendor'/> +<%static:css group='style-course'/> <%include file="/courseware/course_navigation.html" args="active_page='courseware'" /> diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index 2a52b50b09..3f5c1d8c00 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -5,8 +5,11 @@ <%block name="title">${_("{course_number} Courseware").format(course_number=course.display_number_with_default) | h} <%block name="headextra"> - <%static:css group='course'/> - <%include file="../discussion/_js_head_dependencies.html" /> +<%static:css group='style-course-vendor'/> +<%static:css group='style-course'/> + +<%include file="../discussion/_js_head_dependencies.html" /> + % if show_chat: ## It'd be better to have this in a place like lms/css/vendor/candy, diff --git a/lms/templates/courseware/gradebook.html b/lms/templates/courseware/gradebook.html index 2c9f3e8474..8828721555 100644 --- a/lms/templates/courseware/gradebook.html +++ b/lms/templates/courseware/gradebook.html @@ -11,7 +11,8 @@ <%block name="headextra"> - <%static:css group='course'/> +<%static:css group='style-course-vendor'/> +<%static:css group='style-course'/>