diff --git a/src/app/app.scss b/src/app/app.scss
index 2d0a1baa0..d11387ef8 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -226,6 +226,7 @@ ion-app.app-root {
user-select: text;
word-break: break-word;
word-wrap: break-word;
+ white-space: normal;
&[maxHeight],
&[ng-reflect-max-height] {
diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts
index b0261ba75..08bf278b3 100644
--- a/src/components/rich-text-editor/rich-text-editor.ts
+++ b/src/components/rich-text-editor/rich-text-editor.ts
@@ -103,7 +103,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
}
/**
- * Init editor
+ * Init editor.
*/
ngAfterContentInit(): void {
this.domUtils.isRichTextEditorEnabled().then((enabled) => {
diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts
index 48b8a3dcb..9006b3011 100644
--- a/src/components/tabs/tab.ts
+++ b/src/components/tabs/tab.ts
@@ -121,18 +121,14 @@ export class CoreTabComponent implements OnInit, OnDestroy {
this.showHideNavBarButtons(true);
// Setup tab scrolling.
- setTimeout(() => {
- // Workaround to solve undefined this.scroll on tab change.
- const scroll: HTMLElement = this.content ? this.content.getScrollElement() :
- this.element.querySelector('ion-content > .scroll-content');
+ this.domUtils.waitElementToExist(() => this.content ? this.content.getScrollElement() :
+ this.element.querySelector('ion-content > .scroll-content')).then((scroll) => {
+ scroll.addEventListener('scroll', (e): void => {
+ this.tabs.showHideTabs(e.target);
+ });
- if (scroll) {
- scroll.onscroll = (e): void => {
- this.tabs.showHideTabs(e.target);
- };
- this.tabs.showHideTabs(scroll);
- }
- }, 1);
+ this.tabs.showHideTabs(scroll);
+ });
}
/**
diff --git a/src/core/block/components/block/block.scss b/src/core/block/components/block/block.scss
index 765a266a8..4029d7ffa 100644
--- a/src/core/block/components/block/block.scss
+++ b/src/core/block/components/block/block.scss
@@ -1,5 +1,6 @@
ion-app.app-root core-block {
position: relative;
+ display: block;
core-loading.core-loading-center {
display: block;
diff --git a/src/core/block/components/course-blocks/core-block-course-blocks.html b/src/core/block/components/course-blocks/core-block-course-blocks.html
index be7bb0c1d..ee6af5a26 100644
--- a/src/core/block/components/course-blocks/core-block-course-blocks.html
+++ b/src/core/block/components/course-blocks/core-block-course-blocks.html
@@ -2,13 +2,15 @@
- 0" [class.core-hide-blocks]="hideBlocks" class="core-course-blocks-side">
-
-
-
-
-
-
-
-
-
+
0" [class.core-hide-blocks]="hideBlocks" class="core-course-blocks-side">
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/block/components/course-blocks/course-blocks.scss b/src/core/block/components/course-blocks/course-blocks.scss
index e9f9e228a..cc2c9f3c0 100644
--- a/src/core/block/components/course-blocks/course-blocks.scss
+++ b/src/core/block/components/course-blocks/course-blocks.scss
@@ -1,8 +1,5 @@
-$core-side-blocks-max-small-width: 300px;
-$core-side-blocks-min-small-width: 25%;
-
-$core-side-blocks-max-width: 320px;
-$core-side-blocks-min-width: 30%;
+$core-side-blocks-max-width: 30%;
+$core-side-blocks-min-width: 280px;
.core-course-block-with-blocks > .scroll-content {
overflow-y: visible;
@@ -10,15 +7,8 @@ $core-side-blocks-min-width: 30%;
ion-app.app-root core-block-course-blocks {
- &.core-no-blocks {
- .core-course-blocks-content > ion-content {
- height: auto;
-
- > .scroll-content {
- overflow-y: visible;
- position: relative;
- }
- }
+ &.core-no-blocks .core-course-blocks-content {
+ height: auto;
}
&.core-has-blocks {
@@ -33,49 +23,51 @@ ion-app.app-root core-block-course-blocks {
flex-wrap: nowrap;
.core-course-blocks-content {
- min-width: calc(100% - #{($core-side-blocks-max-small-width)});
- max-width: calc(100% - #{($core-side-blocks-min-small-width)});
- z-index: 0;
- flex: 1;
box-shadow: none !important;
+ flex-grow: 1;
+ max-width: 100%;
}
- ion-content.core-course-blocks-side {
- transform: none !important;
- position: sticky;
- @include position(0, 0, 0, auto);
- z-index: 30;
- max-width: $core-side-blocks-max-small-width;
- min-width: $core-side-blocks-min-small-width;
- @include border-start(1px, solid, $list-md-border-color);
- }
- }
-
- @include media-breakpoint-up(lg) {
- .core-course-blocks-content {
- min-width: calc(100% - #{($core-side-blocks-max-width)});
- max-width: calc(100% - #{($core-side-blocks-min-width)});
- }
-
- ion-content.core-course-blocks-side {
+ div.core-course-blocks-side {
+ position: relative;
+ @include position(auto, 0, auto, auto);
max-width: $core-side-blocks-max-width;
min-width: $core-side-blocks-min-width;
+ @include border-start(1px, solid, $list-md-border-color);
+
+ .core-course-blocks-side-scroll {
+ position: absolute;
+ top: 0;
+ max-width: 100%;
+ min-width: 100%;
+
+ &.core-course-blocks-fixed-bottom {
+ position: fixed;
+ bottom: 0;
+ top: auto;
+ transform: none !important;
+ }
+
+ core-block {
+ max-width: $core-side-blocks-max-width;
+ min-width: $core-side-blocks-min-width;
+ }
+ }
}
}
@include media-breakpoint-down(sm) {
// Disable scroll on individual columns.
- .core-course-blocks-content > ion-content,
- ion-content.core-course-blocks-side {
+ div.core-course-blocks-side {
+ transform: none !important;
height: auto;
&.core-hide-blocks {
display: none;
}
- > .scroll-content {
- overflow-y: visible;
- position: relative;
+ .core-course-blocks-side-scroll {
+ transform: none !important;
}
}
}
diff --git a/src/core/block/components/course-blocks/course-blocks.ts b/src/core/block/components/course-blocks/course-blocks.ts
index 744dd4326..574dc3c8b 100644
--- a/src/core/block/components/course-blocks/course-blocks.ts
+++ b/src/core/block/components/course-blocks/course-blocks.ts
@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef, Optional } from '@angular/core';
+import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef, OnDestroy } from '@angular/core';
import { Content } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreAppProvider } from '@providers/app';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreBlockComponent } from '../block/block';
import { CoreBlockHelperProvider } from '../../providers/helper';
@@ -26,7 +27,7 @@ import { CoreBlockHelperProvider } from '../../providers/helper';
selector: 'core-block-course-blocks',
templateUrl: 'core-block-course-blocks.html',
})
-export class CoreBlockCourseBlocksComponent implements OnInit {
+export class CoreBlockCourseBlocksComponent implements OnInit, OnDestroy {
@Input() courseId: number;
@Input() hideBlocks = false;
@@ -38,13 +39,17 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
blocks = [];
protected element: HTMLElement;
- protected parentContent: HTMLElement;
+ protected lastScroll;
+ protected translationY = 0;
+ protected blocksScrollHeight = 0;
+ protected sideScroll: HTMLElement;
+ protected vpHeight = 0; // Viewport height.
+ protected scrollWorking = false;
constructor(private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider,
protected blockHelper: CoreBlockHelperProvider, element: ElementRef,
- @Optional() content: Content) {
+ protected content: Content, protected appProvider: CoreAppProvider) {
this.element = element.nativeElement;
- this.parentContent = content.getElementRef().nativeElement;
}
/**
@@ -53,9 +58,77 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
ngOnInit(): void {
this.loadContent().finally(() => {
this.dataLoaded = true;
+
+ window.addEventListener('resize', this.initScroll.bind(this));
});
}
+ /**
+ * Setup scrolling.
+ */
+ protected initScroll(): void {
+ const scroll: HTMLElement = this.content && this.content.getScrollElement();
+
+ this.domUtils.waitElementToExist(() => scroll && scroll.querySelector('.core-course-blocks-side')).then((sideElement) => {
+ const contentHeight = parseInt(this.content.getNativeElement().querySelector('.scroll-content').scrollHeight, 10);
+
+ this.sideScroll = scroll.querySelector('.core-course-blocks-side-scroll');
+ this.blocksScrollHeight = this.sideScroll.scrollHeight;
+ this.vpHeight = sideElement.clientHeight;
+
+ // Check if needed and event was not init before.
+ if (this.appProvider.isWide() && this.vpHeight && contentHeight > this.vpHeight &&
+ this.blocksScrollHeight > this.vpHeight) {
+ if (typeof this.lastScroll == 'undefined') {
+ this.lastScroll = 0;
+ scroll.addEventListener('scroll', this.scrollFunction.bind(this));
+ }
+ this.scrollWorking = true;
+ } else {
+ this.sideScroll.style.transform = 'translate(0, 0)';
+ this.sideScroll.classList.remove('core-course-blocks-fixed-bottom');
+ this.scrollWorking = false;
+ }
+ });
+ }
+
+ /**
+ * Scroll function that moves the sidebar if needed.
+ *
+ * @param {Event} e Event to get the target from.
+ */
+ protected scrollFunction(e: Event): void {
+ if (!this.scrollWorking) {
+ return;
+ }
+
+ const target: any = e.target,
+ top = parseInt(target.scrollTop, 10),
+ goingUp = top < this.lastScroll;
+ if (goingUp) {
+ this.sideScroll.classList.remove('core-course-blocks-fixed-bottom');
+ if (top <= this.translationY ) {
+ // Fixed to top.
+ this.translationY = top;
+ this.sideScroll.style.transform = 'translate(0, ' + this.translationY + 'px)';
+ }
+ } else if (top - this.translationY >= (this.blocksScrollHeight - this.vpHeight)) {
+ // Fixed to bottom.
+ this.sideScroll.classList.add('core-course-blocks-fixed-bottom');
+ this.translationY = top - (this.blocksScrollHeight - this.vpHeight);
+ this.sideScroll.style.transform = 'translate(0, ' + this.translationY + 'px)';
+ }
+
+ this.lastScroll = top;
+ }
+
+ /**
+ * Component destroyed.
+ */
+ ngOnDestroy(): void {
+ window.removeEventListener('resize', this.initScroll);
+ }
+
/**
* Invalidate blocks data.
*
@@ -95,11 +168,13 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
this.element.classList.add('core-has-blocks');
this.element.classList.remove('core-no-blocks');
- this.parentContent.classList.add('core-course-block-with-blocks');
+ this.content.getElementRef().nativeElement.classList.add('core-course-block-with-blocks');
+
+ this.initScroll();
} else {
this.element.classList.remove('core-has-blocks');
this.element.classList.add('core-no-blocks');
- this.parentContent.classList.remove('core-course-block-with-blocks');
+ this.content.getElementRef().nativeElement.classList.remove('core-course-block-with-blocks');
}
});
}
diff --git a/src/core/course/components/format/core-course-format.html b/src/core/course/components/format/core-course-format.html
index 028dc2785..2d01ef5a1 100644
--- a/src/core/course/components/format/core-course-format.html
+++ b/src/core/course/components/format/core-course-format.html
@@ -6,69 +6,67 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = 0)" class="core-format-progress-list">
+
+
+ = 0 && course.completionusertracked !== false">
+
+
+
+
+
+
+
+
+
+
+
-
-
- = 0)" class="core-format-progress-list">
-
-
![]()
-
- = 0 && course.completionusertracked !== false">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
diff --git a/src/core/sitehome/components/index/core-sitehome-index.html b/src/core/sitehome/components/index/core-sitehome-index.html
index 389a968ac..7bc8292fe 100644
--- a/src/core/sitehome/components/index/core-sitehome-index.html
+++ b/src/core/sitehome/components/index/core-sitehome-index.html
@@ -1,30 +1,28 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
+
+
+ 0">
+
+
+
+
+
+
+
+
+
-
- 0">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts
index e0a99c94c..8776024e7 100644
--- a/src/providers/utils/dom.ts
+++ b/src/providers/utils/dom.ts
@@ -702,6 +702,45 @@ export class CoreDomUtilsProvider {
return this.instances[id];
}
+ /**
+ * Wait an element to exists using the findFunction.
+ *
+ * @param {Function} findFunction The function used to find the element.
+ * @return {Promise
} Resolved if found, rejected if too many tries.
+ */
+ waitElementToExist(findFunction: Function): Promise {
+ const promiseInterval = {
+ promise: null,
+ resolve: null,
+ reject: null
+ };
+
+ let tries = 100;
+
+ promiseInterval.promise = new Promise((resolve, reject): void => {
+ promiseInterval.resolve = resolve;
+ promiseInterval.reject = reject;
+ });
+
+ const clear = setInterval(() => {
+ const element: HTMLElement = findFunction();
+
+ if (element) {
+ clearInterval(clear);
+ promiseInterval.resolve(element);
+ } else {
+ tries--;
+
+ if (tries <= 0) {
+ clearInterval(clear);
+ promiseInterval.reject();
+ }
+ }
+ }, 100);
+
+ return promiseInterval.promise;
+ }
+
/**
* Handle bootstrap tooltips in a certain element.
*
diff --git a/src/theme/format-text.scss b/src/theme/format-text.scss
index d14efd148..a14ee433e 100644
--- a/src/theme/format-text.scss
+++ b/src/theme/format-text.scss
@@ -9,6 +9,10 @@ ion-app.app-root core-rich-text-editor .core-rte-editor {
margin-bottom: 1rem;
}
+ .no-overflow {
+ overflow: auto;
+ }
+
// Fix lists styles in core-format-text.
ul {
padding-left: 1rem;