Merge pull request #2053 from crazyserver/MOBILE-3068

Mobile 3068
main
Juan Leyva 2019-08-09 16:55:57 +02:00 committed by GitHub
commit 85ef2a8e12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 446 additions and 319 deletions

View File

@ -6,9 +6,13 @@
-webkit-padding-start: 0; -webkit-padding-start: 0;
li { li {
padding: 0 .2em; padding: .2em;
display: inline; display: inline-block;
}
a {
@extend ion-badge;
@extend .badge-md;
text-decoration: none;
} }
.s20 { .s20 {
font-size: 1.5em; font-size: 1.5em;
@ -80,3 +84,5 @@
} }
} }
} }
}
}

View File

@ -47,7 +47,7 @@ export class AddonBlockCommentsHandler extends CoreBlockBaseHandler {
component: CoreBlockOnlyTitleComponent, component: CoreBlockOnlyTitleComponent,
link: 'CoreCommentsViewerPage', link: 'CoreCommentsViewerPage',
linkParams: { contextLevel: contextLevel, instanceId: instanceId, linkParams: { contextLevel: contextLevel, instanceId: instanceId,
component: 'block_comments', area: 'page_comments', itemId: 0 } componentName: 'block_comments', area: 'page_comments', itemId: 0 }
}; };
} }
} }

View File

@ -8,93 +8,99 @@
-webkit-padding-start: 0; -webkit-padding-start: 0;
li { li {
padding: 0 .2em; padding: .2em;
display: inline; display: inline-block;
a {
@extend ion-badge;
@extend .badge-md;
text-decoration: none;
} }
} .s20 {
}
.tag_cloud .s20 {
font-size: 2.7em; font-size: 2.7em;
} }
.tag_cloud .s19 { .s19 {
font-size: 2.6em; font-size: 2.6em;
} }
.tag_cloud .s18 { .s18 {
font-size: 2.5em; font-size: 2.5em;
} }
.tag_cloud .s17 { .s17 {
font-size: 2.4em; font-size: 2.4em;
} }
.tag_cloud .s16 { .s16 {
font-size: 2.3em; font-size: 2.3em;
} }
.tag_cloud .s15 { .s15 {
font-size: 2.2em; font-size: 2.2em;
} }
.tag_cloud .s14 { .s14 {
font-size: 2.1em; font-size: 2.1em;
} }
.tag_cloud .s13 { .s13 {
font-size: 2em; font-size: 2em;
} }
.tag_cloud .s12 { .s12 {
font-size: 1.9em; font-size: 1.9em;
} }
.tag_cloud .s11 { .s11 {
font-size: 1.8em; font-size: 1.8em;
} }
.tag_cloud .s10 { .s10 {
font-size: 1.7em; font-size: 1.7em;
} }
.tag_cloud .s9 { .s9 {
font-size: 1.6em; font-size: 1.6em;
} }
.tag_cloud .s8 { .s8 {
font-size: 1.5em; font-size: 1.5em;
} }
.tag_cloud .s7 { .s7 {
font-size: 1.4em; font-size: 1.4em;
} }
.tag_cloud .s6 { .s6 {
font-size: 1.3em; font-size: 1.3em;
} }
.tag_cloud .s5 { .s5 {
font-size: 1.2em; font-size: 1.2em;
} }
.tag_cloud .s4 { .s4 {
font-size: 1.1em; font-size: 1.1em;
} }
.tag_cloud .s3 { .s3 {
font-size: 1em; font-size: 1em;
} }
.tag_cloud .s2 { .s2 {
font-size: 0.9em; font-size: 0.9em;
} }
.tag_cloud .s1 { .s1 {
font-size: 0.8em; font-size: 0.8em;
} }
.tag_cloud .s0 { .s0 {
font-size: 0.7em; font-size: 0.7em;
} }
} }
} }
}
}
}

View File

@ -18,6 +18,7 @@ import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModGlossaryProvider } from './glossary';
/** /**
* Content links handler for glossary new entry. * Content links handler for glossary new entry.
@ -31,7 +32,7 @@ export class AddonModGlossaryEditLinkHandler extends CoreContentLinksHandlerBase
pattern = /\/mod\/glossary\/edit\.php.*([\?\&](cmid)=\d+)/; pattern = /\/mod\/glossary\/edit\.php.*([\?\&](cmid)=\d+)/;
constructor(private linkHelper: CoreContentLinksHelperProvider, private courseProvider: CoreCourseProvider, constructor(private linkHelper: CoreContentLinksHelperProvider, private courseProvider: CoreCourseProvider,
private domUtils: CoreDomUtilsProvider) { private domUtils: CoreDomUtilsProvider, private glossaryProvider: AddonModGlossaryProvider) {
super(); super();
} }
@ -53,14 +54,16 @@ export class AddonModGlossaryEditLinkHandler extends CoreContentLinksHandlerBase
cmId = parseInt(params.cmid, 10); cmId = parseInt(params.cmid, 10);
this.courseProvider.getModuleBasicInfo(cmId, siteId).then((module) => { this.courseProvider.getModuleBasicInfo(cmId, siteId).then((module) => {
return this.glossaryProvider.getGlossary(module.course, module.id).then((glossary) => {
const pageParams = { const pageParams = {
courseId: module.course, courseId: module.course,
module: module, module: module,
glossary: module.module, glossary: glossary,
entry: null // It does not support entry editing. entry: null // It does not support entry editing.
}; };
return this.linkHelper.goInSite(navCtrl, 'AddonModGlossaryEditPage', pageParams, siteId); this.linkHelper.goInSite(navCtrl, 'AddonModGlossaryEditPage', pageParams, siteId);
});
}).finally(() => { }).finally(() => {
// Just in case. In fact we need to dismiss the modal before showing a toast or error message. // Just in case. In fact we need to dismiss the modal before showing a toast or error message.
modal.dismiss(); modal.dismiss();

View File

@ -27,7 +27,7 @@ import { AddonModGlossaryProvider } from './glossary';
export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBase { export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonModGlossaryEntryLinkHandler'; name = 'AddonModGlossaryEntryLinkHandler';
featureName = 'CoreCourseModuleDelegate_AddonModGlossary'; featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
pattern = /\/mod\/glossary\/showentry\.php.*([\&\?]eid=\d+)/; pattern = /\/mod\/glossary\/(showentry|view)\.php.*([\&\?](eid|g|mode|hook)=\d+)/;
constructor( constructor(
private domUtils: CoreDomUtilsProvider, private domUtils: CoreDomUtilsProvider,
@ -51,7 +51,13 @@ export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBas
return [{ return [{
action: (siteId, navCtrl?): void => { action: (siteId, navCtrl?): void => {
const modal = this.domUtils.showModalLoading(); 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; let promise;
if (courseId) { if (courseId) {

View File

@ -226,6 +226,7 @@ ion-app.app-root {
user-select: text; user-select: text;
word-break: break-word; word-break: break-word;
word-wrap: break-word; word-wrap: break-word;
white-space: normal;
&[maxHeight], &[maxHeight],
&[ng-reflect-max-height] { &[ng-reflect-max-height] {

View File

@ -103,7 +103,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
} }
/** /**
* Init editor * Init editor.
*/ */
ngAfterContentInit(): void { ngAfterContentInit(): void {
this.domUtils.isRichTextEditorEnabled().then((enabled) => { this.domUtils.isRichTextEditorEnabled().then((enabled) => {

View File

@ -121,18 +121,14 @@ export class CoreTabComponent implements OnInit, OnDestroy {
this.showHideNavBarButtons(true); this.showHideNavBarButtons(true);
// Setup tab scrolling. // Setup tab scrolling.
setTimeout(() => { this.domUtils.waitElementToExist(() => this.content ? this.content.getScrollElement() :
// Workaround to solve undefined this.scroll on tab change. this.element.querySelector('ion-content > .scroll-content')).then((scroll) => {
const scroll: HTMLElement = this.content ? this.content.getScrollElement() : scroll.addEventListener('scroll', (e): void => {
this.element.querySelector('ion-content > .scroll-content');
if (scroll) {
scroll.onscroll = (e): void => {
this.tabs.showHideTabs(e.target); this.tabs.showHideTabs(e.target);
}; });
this.tabs.showHideTabs(scroll); this.tabs.showHideTabs(scroll);
} });
}, 1);
} }
/** /**

View File

@ -1,5 +1,6 @@
ion-app.app-root core-block { ion-app.app-root core-block {
position: relative; position: relative;
display: block;
core-loading.core-loading-center { core-loading.core-loading-center {
display: block; display: block;

View File

@ -2,7 +2,8 @@
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<ion-content *ngIf="blocks && blocks.length > 0" [class.core-hide-blocks]="hideBlocks" class="core-course-blocks-side"> <div *ngIf="blocks && blocks.length > 0" [class.core-hide-blocks]="hideBlocks" class="core-course-blocks-side">
<div class="core-course-blocks-side-scroll">
<core-loading [hideUntil]="dataLoaded" class="core-loading-center"> <core-loading [hideUntil]="dataLoaded" class="core-loading-center">
<ion-list> <ion-list>
<!-- Course blocks. --> <!-- Course blocks. -->
@ -11,4 +12,5 @@
</ng-container> </ng-container>
</ion-list> </ion-list>
</core-loading> </core-loading>
</ion-content> </div>
</div>

View File

@ -1,8 +1,5 @@
$core-side-blocks-max-small-width: 300px; $core-side-blocks-max-width: 30%;
$core-side-blocks-min-small-width: 25%; $core-side-blocks-min-width: 280px;
$core-side-blocks-max-width: 320px;
$core-side-blocks-min-width: 30%;
.core-course-block-with-blocks > .scroll-content { .core-course-block-with-blocks > .scroll-content {
overflow-y: visible; overflow-y: visible;
@ -10,15 +7,8 @@ $core-side-blocks-min-width: 30%;
ion-app.app-root core-block-course-blocks { ion-app.app-root core-block-course-blocks {
&.core-no-blocks { &.core-no-blocks .core-course-blocks-content {
.core-course-blocks-content > ion-content {
height: auto; height: auto;
> .scroll-content {
overflow-y: visible;
position: relative;
}
}
} }
&.core-has-blocks { &.core-has-blocks {
@ -33,49 +23,51 @@ ion-app.app-root core-block-course-blocks {
flex-wrap: nowrap; flex-wrap: nowrap;
.core-course-blocks-content { .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; box-shadow: none !important;
flex-grow: 1;
max-width: 100%;
} }
ion-content.core-course-blocks-side { div.core-course-blocks-side {
transform: none !important; position: relative;
position: sticky; @include position(auto, 0, auto, auto);
@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 {
max-width: $core-side-blocks-max-width; max-width: $core-side-blocks-max-width;
min-width: $core-side-blocks-min-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) { @include media-breakpoint-down(sm) {
// Disable scroll on individual columns. // Disable scroll on individual columns.
.core-course-blocks-content > ion-content, div.core-course-blocks-side {
ion-content.core-course-blocks-side { transform: none !important;
height: auto; height: auto;
&.core-hide-blocks { &.core-hide-blocks {
display: none; display: none;
} }
> .scroll-content { .core-course-blocks-side-scroll {
overflow-y: visible; transform: none !important;
position: relative;
} }
} }
} }

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 { Content } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreAppProvider } from '@providers/app';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreBlockComponent } from '../block/block'; import { CoreBlockComponent } from '../block/block';
import { CoreBlockHelperProvider } from '../../providers/helper'; import { CoreBlockHelperProvider } from '../../providers/helper';
@ -26,7 +27,7 @@ import { CoreBlockHelperProvider } from '../../providers/helper';
selector: 'core-block-course-blocks', selector: 'core-block-course-blocks',
templateUrl: 'core-block-course-blocks.html', templateUrl: 'core-block-course-blocks.html',
}) })
export class CoreBlockCourseBlocksComponent implements OnInit { export class CoreBlockCourseBlocksComponent implements OnInit, OnDestroy {
@Input() courseId: number; @Input() courseId: number;
@Input() hideBlocks = false; @Input() hideBlocks = false;
@ -38,13 +39,17 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
blocks = []; blocks = [];
protected element: HTMLElement; 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, constructor(private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider,
protected blockHelper: CoreBlockHelperProvider, element: ElementRef, protected blockHelper: CoreBlockHelperProvider, element: ElementRef,
@Optional() content: Content) { protected content: Content, protected appProvider: CoreAppProvider) {
this.element = element.nativeElement; this.element = element.nativeElement;
this.parentContent = content.getElementRef().nativeElement;
} }
/** /**
@ -53,9 +58,77 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.loadContent().finally(() => { this.loadContent().finally(() => {
this.dataLoaded = true; 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. * Invalidate blocks data.
* *
@ -95,11 +168,13 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
this.element.classList.add('core-has-blocks'); this.element.classList.add('core-has-blocks');
this.element.classList.remove('core-no-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 { } else {
this.element.classList.remove('core-has-blocks'); this.element.classList.remove('core-has-blocks');
this.element.classList.add('core-no-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');
} }
}); });
} }

View File

@ -156,7 +156,7 @@ export class CoreCommentsViewerPage implements OnDestroy {
this.canAddComments = this.addDeleteCommentsAvailable && response.canpost; this.canAddComments = this.addDeleteCommentsAvailable && response.canpost;
const comments = response.comments.sort((a, b) => b.timecreated - a.timecreated); 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) => { return Promise.all(comments.map((comment) => {
// Get the user profile image. // Get the user profile image.

View File

@ -6,7 +6,6 @@
</core-navbar-buttons> </core-navbar-buttons>
<core-block-course-blocks [courseId]="course.id" [hideBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore" [downloadEnabled]="downloadEnabled"> <core-block-course-blocks [courseId]="course.id" [hideBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore" [downloadEnabled]="downloadEnabled">
<ion-content>
<!-- Default course format. --> <!-- Default course format. -->
<core-dynamic-component [component]="courseFormatComponent" [data]="data"> <core-dynamic-component [component]="courseFormatComponent" [data]="data">
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
@ -68,7 +67,6 @@
</ion-buttons> </ion-buttons>
</core-dynamic-component> </core-dynamic-component>
</ion-content>
</core-block-course-blocks> </core-block-course-blocks>
<!-- Template to render a section. --> <!-- Template to render a section. -->

View File

@ -1,5 +1,4 @@
<core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled"> <core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
<ion-content>
<core-loading [hideUntil]="dataLoaded"> <core-loading [hideUntil]="dataLoaded">
<ion-list> <ion-list>
<!-- Site home main contents. --> <!-- Site home main contents. -->
@ -26,5 +25,4 @@
<core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box> <core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
</core-loading> </core-loading>
</ion-content>
</core-block-course-blocks> </core-block-course-blocks>

View File

@ -702,6 +702,45 @@ export class CoreDomUtilsProvider {
return this.instances[id]; return this.instances[id];
} }
/**
* Wait an element to exists using the findFunction.
*
* @param {Function} findFunction The function used to find the element.
* @return {Promise<HTMLElement>} Resolved if found, rejected if too many tries.
*/
waitElementToExist(findFunction: Function): Promise<HTMLElement> {
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. * Handle bootstrap tooltips in a certain element.
* *

View File

@ -9,6 +9,10 @@ ion-app.app-root core-rich-text-editor .core-rte-editor {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.no-overflow {
overflow: auto;
}
// Fix lists styles in core-format-text. // Fix lists styles in core-format-text.
ul { ul {
padding-left: 1rem; padding-left: 1rem;