diff --git a/src/addon/block/blogtags/blogtags.scss b/src/addon/block/blogtags/blogtags.scss index 859b876da..a974b45cb 100644 --- a/src/addon/block/blogtags/blogtags.scss +++ b/src/addon/block/blogtags/blogtags.scss @@ -6,77 +6,83 @@ -webkit-padding-start: 0; li { - padding: 0 .2em; - display: inline; + padding: .2em; + display: inline-block; + + a { + @extend ion-badge; + @extend .badge-md; + text-decoration: none; + } + .s20 { + font-size: 1.5em; + font-weight: bold; + } + + .s19 { + font-size: 1.5em; + } + + .s18 { + font-size: 1.4em; + font-weight: bold; + } + + .s17 { + font-size: 1.4em; + } + + .s16 { + font-size: 1.3em; + font-weight: bold; + } + + .s15 { + font-size: 1.3em; + } + + .s14 { + font-size: 1.2em; + font-weight: bold; + } + + .s13 { + font-size: 1.2em; + } + + .s12, + .s11 { + font-size: 1.1em; + font-weight: bold; + } + + .s10, + .s9 { + font-size: 1.1em; + } + + .s8, + .s7 { + font-size: 1em; + font-weight: bold; + } + + .s6, + .s5 { + font-size: 1em; + } + + .s4, + .s3 { + font-size: 0.9em; + font-weight: bold; + } + + .s2, + .s1 { + font-size: 0.9em; + } } } - .s20 { - font-size: 1.5em; - font-weight: bold; - } - - .s19 { - font-size: 1.5em; - } - - .s18 { - font-size: 1.4em; - font-weight: bold; - } - - .s17 { - font-size: 1.4em; - } - - .s16 { - font-size: 1.3em; - font-weight: bold; - } - - .s15 { - font-size: 1.3em; - } - - .s14 { - font-size: 1.2em; - font-weight: bold; - } - - .s13 { - font-size: 1.2em; - } - - .s12, - .s11 { - font-size: 1.1em; - font-weight: bold; - } - - .s10, - .s9 { - font-size: 1.1em; - } - - .s8, - .s7 { - font-size: 1em; - font-weight: bold; - } - - .s6, - .s5 { - font-size: 1em; - } - - .s4, - .s3 { - font-size: 0.9em; - font-weight: bold; - } - - .s2, - .s1 { - font-size: 0.9em; - } } } \ No newline at end of file diff --git a/src/addon/block/comments/providers/block-handler.ts b/src/addon/block/comments/providers/block-handler.ts index 81e5b2c15..ada6e1654 100644 --- a/src/addon/block/comments/providers/block-handler.ts +++ b/src/addon/block/comments/providers/block-handler.ts @@ -47,7 +47,7 @@ export class AddonBlockCommentsHandler extends CoreBlockBaseHandler { component: CoreBlockOnlyTitleComponent, link: 'CoreCommentsViewerPage', linkParams: { contextLevel: contextLevel, instanceId: instanceId, - component: 'block_comments', area: 'page_comments', itemId: 0 } + componentName: 'block_comments', area: 'page_comments', itemId: 0 } }; } } diff --git a/src/addon/block/tags/tags.scss b/src/addon/block/tags/tags.scss index cd3df32eb..f4c54d167 100644 --- a/src/addon/block/tags/tags.scss +++ b/src/addon/block/tags/tags.scss @@ -8,93 +8,99 @@ -webkit-padding-start: 0; li { - padding: 0 .2em; - display: inline; + padding: .2em; + display: inline-block; + + a { + @extend ion-badge; + @extend .badge-md; + text-decoration: none; + } + .s20 { + font-size: 2.7em; + } + + .s19 { + font-size: 2.6em; + } + + .s18 { + font-size: 2.5em; + } + + .s17 { + font-size: 2.4em; + } + + .s16 { + font-size: 2.3em; + } + + .s15 { + font-size: 2.2em; + } + + .s14 { + font-size: 2.1em; + } + + .s13 { + font-size: 2em; + } + + .s12 { + font-size: 1.9em; + } + + .s11 { + font-size: 1.8em; + } + + .s10 { + font-size: 1.7em; + } + + .s9 { + font-size: 1.6em; + } + + .s8 { + font-size: 1.5em; + } + + .s7 { + font-size: 1.4em; + } + + .s6 { + font-size: 1.3em; + } + + .s5 { + font-size: 1.2em; + } + + .s4 { + font-size: 1.1em; + } + + .s3 { + font-size: 1em; + } + + .s2 { + font-size: 0.9em; + } + + .s1 { + font-size: 0.8em; + } + + .s0 { + font-size: 0.7em; + } } } } - .tag_cloud .s20 { - font-size: 2.7em; - } - - .tag_cloud .s19 { - font-size: 2.6em; - } - - .tag_cloud .s18 { - font-size: 2.5em; - } - - .tag_cloud .s17 { - font-size: 2.4em; - } - - .tag_cloud .s16 { - font-size: 2.3em; - } - - .tag_cloud .s15 { - font-size: 2.2em; - } - - .tag_cloud .s14 { - font-size: 2.1em; - } - - .tag_cloud .s13 { - font-size: 2em; - } - - .tag_cloud .s12 { - font-size: 1.9em; - } - - .tag_cloud .s11 { - font-size: 1.8em; - } - - .tag_cloud .s10 { - font-size: 1.7em; - } - - .tag_cloud .s9 { - font-size: 1.6em; - } - - .tag_cloud .s8 { - font-size: 1.5em; - } - - .tag_cloud .s7 { - font-size: 1.4em; - } - - .tag_cloud .s6 { - font-size: 1.3em; - } - - .tag_cloud .s5 { - font-size: 1.2em; - } - - .tag_cloud .s4 { - font-size: 1.1em; - } - - .tag_cloud .s3 { - font-size: 1em; - } - - .tag_cloud .s2 { - font-size: 0.9em; - } - - .tag_cloud .s1 { - font-size: 0.8em; - } - - .tag_cloud .s0 { - font-size: 0.7em; - } } } \ No newline at end of file diff --git a/src/addon/mod/glossary/providers/edit-link-handler.ts b/src/addon/mod/glossary/providers/edit-link-handler.ts index c2f34305c..c86557aee 100644 --- a/src/addon/mod/glossary/providers/edit-link-handler.ts +++ b/src/addon/mod/glossary/providers/edit-link-handler.ts @@ -18,6 +18,7 @@ import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { AddonModGlossaryProvider } from './glossary'; /** * Content links handler for glossary new entry. @@ -31,7 +32,7 @@ export class AddonModGlossaryEditLinkHandler extends CoreContentLinksHandlerBase pattern = /\/mod\/glossary\/edit\.php.*([\?\&](cmid)=\d+)/; constructor(private linkHelper: CoreContentLinksHelperProvider, private courseProvider: CoreCourseProvider, - private domUtils: CoreDomUtilsProvider) { + private domUtils: CoreDomUtilsProvider, private glossaryProvider: AddonModGlossaryProvider) { super(); } @@ -53,14 +54,16 @@ export class AddonModGlossaryEditLinkHandler extends CoreContentLinksHandlerBase cmId = parseInt(params.cmid, 10); this.courseProvider.getModuleBasicInfo(cmId, siteId).then((module) => { - const pageParams = { - courseId: module.course, - module: module, - glossary: module.module, - entry: null // It does not support entry editing. - }; + return this.glossaryProvider.getGlossary(module.course, module.id).then((glossary) => { + const pageParams = { + courseId: module.course, + module: module, + glossary: glossary, + entry: null // It does not support entry editing. + }; - return this.linkHelper.goInSite(navCtrl, 'AddonModGlossaryEditPage', pageParams, siteId); + this.linkHelper.goInSite(navCtrl, 'AddonModGlossaryEditPage', pageParams, siteId); + }); }).finally(() => { // Just in case. In fact we need to dismiss the modal before showing a toast or error message. modal.dismiss(); diff --git a/src/addon/mod/glossary/providers/entry-link-handler.ts b/src/addon/mod/glossary/providers/entry-link-handler.ts index 953aeb41a..99acce7b7 100644 --- a/src/addon/mod/glossary/providers/entry-link-handler.ts +++ b/src/addon/mod/glossary/providers/entry-link-handler.ts @@ -27,7 +27,7 @@ import { AddonModGlossaryProvider } from './glossary'; export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBase { name = 'AddonModGlossaryEntryLinkHandler'; featureName = 'CoreCourseModuleDelegate_AddonModGlossary'; - pattern = /\/mod\/glossary\/showentry\.php.*([\&\?]eid=\d+)/; + pattern = /\/mod\/glossary\/(showentry|view)\.php.*([\&\?](eid|g|mode|hook)=\d+)/; constructor( private domUtils: CoreDomUtilsProvider, @@ -51,7 +51,13 @@ export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBas return [{ action: (siteId, navCtrl?): void => { const modal = this.domUtils.showModalLoading(); - const entryId = parseInt(params.eid, 10); + let entryId; + if (params.mode == 'entry') { + entryId = parseInt(params.hook, 10); + } else { + entryId = parseInt(params.eid, 10); + } + let promise; if (courseId) { diff --git a/src/app/app.scss b/src/app/app.scss index 8bbc87959..f2e7d5892 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 @@ - - - - - - - - - - +
+
+ + + + + + + + +
+
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/comments/pages/viewer/viewer.ts b/src/core/comments/pages/viewer/viewer.ts index 0f606feee..e84d27e0c 100644 --- a/src/core/comments/pages/viewer/viewer.ts +++ b/src/core/comments/pages/viewer/viewer.ts @@ -156,7 +156,7 @@ export class CoreCommentsViewerPage implements OnDestroy { this.canAddComments = this.addDeleteCommentsAvailable && response.canpost; const comments = response.comments.sort((a, b) => b.timecreated - a.timecreated); - this.canLoadMore = comments.length >= CoreCommentsProvider.pageSize; + this.canLoadMore = comments.length > 0 && comments.length >= CoreCommentsProvider.pageSize; return Promise.all(comments.map((comment) => { // Get the user profile image. 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 @@ - - - - - - -
- - - + + + + + +
+ + + +
+
+ + + + +
+
+ + + +
+
+ + +
+ + + +
- - - -
- -
- - - -
-
- - -
- - - - -
- - -
- - - - - + +
+ + + + - + + - -
- - - - - + +
+
+ + + + -
- + 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 @@ - - - - - - - - + + + + + + + - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + 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;