Merge pull request #3165 from crazyserver/MOBILE-3814

Mobile 3814
main
Dani Palou 2022-03-10 09:44:37 +01:00 committed by GitHub
commit f760560481
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 352 additions and 235 deletions

View File

@ -129,8 +129,8 @@
[attr.aria-label]="'addon.messages.newmessages' | translate">
<ion-icon name="fas-arrow-down" aria-hidden="true"></ion-icon>
<span class="sr-only">{{ 'addon.messages.newmessages' | translate }}</span>
<ion-badge class="core-discussion-messages-badge">{{ newMessages }}</ion-badge>
</ion-fab-button>
<ion-badge class="core-discussion-messages-badge">{{ newMessages }}</ion-badge>
</ion-fab>
</ion-content>
<ion-footer class="footer-adjustable" *ngIf="loaded && (!conversationId || conversation)">

View File

@ -499,13 +499,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
*/
protected setNewMessagesBadge(addMessages: number): void {
if (this.newMessages == 0 && addMessages > 0) {
// Setup scrolling.
this.content!.scrollEvents = true;
this.scrollFunction();
} else if (this.newMessages > 0 && addMessages == 0) {
// Remove scrolling.
this.content!.scrollEvents = false;
}
this.newMessages = addMessages;
@ -1098,8 +1092,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
// Leave time for the view to be rendered.
await CoreUtils.nextTicks(5);
if (!this.viewDestroyed) {
this.content!.scrollToBottom(0);
if (!this.viewDestroyed && this.content) {
this.content.scrollToBottom(0);
}
if (force) {
@ -1112,10 +1106,10 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
* Scroll to the first new unread message.
*/
scrollToFirstUnreadMessage(): void {
if (this.newMessages > 0) {
if (this.newMessages > 0 && this.content) {
const messages = Array.from(this.hostElement.querySelectorAll('.addon-message-not-mine'));
CoreDomUtils.scrollToElement(this.content!, <HTMLElement> messages[messages.length - this.newMessages]);
CoreDomUtils.scrollToElement(this.content, <HTMLElement> messages[messages.length - this.newMessages]);
}
}

View File

@ -19,19 +19,12 @@
padding-bottom: 0;
}
ion-fab ion-fab-button {
&::part(native) {
contain: unset;
overflow: visible;
}
.core-discussion-messages-badge {
ion-fab .core-discussion-messages-badge {
position: absolute;
color: var(--addon-messages-discussion-badge-text);
background-color: var(--addon-messages-discussion-badge);
display: block;
@include position(-6px, -6px, null, null);
}
@include position(0, 0, null, null);
}
ion-header ion-toolbar h1 {

View File

@ -8,7 +8,7 @@
{{ 'addon.mod_assign.feedbacknotsupported' | translate }}
</ion-badge>
<p *ngIf="text">
<core-format-text [component]="component" [componentId]="assign.cmid" [collapsible-item]="120" [text]="text"
<core-format-text [component]="component" [componentId]="assign.cmid" collapsible-item [text]="text"
contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course">
</core-format-text>
</p>

View File

@ -8,7 +8,7 @@
{{ 'addon.mod_assign.submissionnotsupported' | translate }}
</ion-badge>
<p *ngIf="text">
<core-format-text [component]="component" [componentId]="assign.cmid" [collapsible-item]="120" [text]="text"
<core-format-text [component]="component" [componentId]="assign.cmid" collapsible-item [text]="text"
contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course">
</core-format-text>
</p>

View File

@ -3,8 +3,8 @@
<ion-label>
<h2>{{ plugin.name }}</h2>
<p>
<core-format-text [component]="component" [componentId]="assign.cmid" [collapsible-item]="120" [text]="text"
contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course">
<core-format-text [component]="component" [componentId]="assign.cmid" collapsible-item [text]="text" contextLevel="module"
[contextInstanceId]="assign.cmid" [courseId]="assign.course">
</core-format-text>
</p>
</ion-label>

View File

@ -4,8 +4,8 @@
<h2>{{ plugin.name }}</h2>
<p *ngIf="words">{{ 'addon.mod_assign.numwords' | translate: {'$a': words} }}</p>
<p>
<core-format-text [component]="component" [componentId]="assign.cmid" [collapsible-item]="120" [text]="text"
contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course">
<core-format-text [component]="component" [componentId]="assign.cmid" collapsible-item [text]="text" contextLevel="module"
[contextInstanceId]="assign.cmid" [courseId]="assign.course">
</core-format-text>
</p>
</ion-label>

View File

@ -45,8 +45,8 @@
</div>
</core-loading>
<core-navigation-bar collapsible-footer *ngIf="loaded && displayNavBar && navigationItems.length > 1" [items]="navigationItems"
previousTranslate="addon.mod_book.navprevtitle" nextTranslate="addon.mod_book.navnexttitle" (action)="changeChapter($event.id)"
slot="fixed">
<core-navigation-bar collapsible-footer appearOnBottom *ngIf="loaded && displayNavBar && navigationItems.length > 1"
[items]="navigationItems" previousTranslate="addon.mod_book.navprevtitle" nextTranslate="addon.mod_book.navnexttitle"
(action)="changeChapter($event.id)" slot="fixed">
</core-navigation-bar>
</ion-content>

View File

@ -61,7 +61,7 @@
(onLoading)="setLoadingComments($event)" [showItem]="true">
</core-comments>
<div collapsible-footer *ngIf="entryLoaded && hasPrevious || hasNext" slot="fixed">
<div collapsible-footer *ngIf="entryLoaded && hasPrevious || hasNext" slot="fixed" appearOnBottom>
<ion-row class="ion-justify-content-between ion-align-items-center ion-no-padding ion-wrap">
<ion-col class="ion-text-start ion-no-padding core-navigation-arrow" size="auto">
<ion-button [disabled]="!hasPrevious" fill="clear" [attr.aria-label]="'core.previous' | translate"

View File

@ -149,7 +149,7 @@
</ion-item>
</ion-card>
<div collapsible-footer *ngIf="feedbackLoaded && completed" slot="fixed">
<div collapsible-footer *ngIf="feedbackLoaded && completed" slot="fixed" appearOnBottom>
<div class="list-item-limited-width adaptable-buttons-row">
<ion-button expand="block" fill="outline" (click)="showAnalysis()" class="ion-text-wrap ion-margin"
*ngIf="access!.canviewanalysis">

View File

@ -33,7 +33,7 @@
</div>
</core-loading>
<core-navigation-bar collapsible-footer *ngIf="loaded && navigationItems.length > 1" [items]="navigationItems"
<core-navigation-bar collapsible-footer appearOnBottom *ngIf="loaded && navigationItems.length > 1" [items]="navigationItems"
(action)="loadItem($event)">
</core-navigation-bar>
</ion-content>

View File

@ -87,7 +87,7 @@
<ion-label>
<h3 class="item-heading">{{ 'addon.mod_lesson.question' | translate }}</h3>
<p>
<core-format-text [component]="component" [componentId]="lesson?.coursemodule" [collapsible-item]="50"
<core-format-text [component]="component" [componentId]="lesson?.coursemodule" collapsible-item
[text]="page.contents" contextLevel="module" [contextInstanceId]="lesson?.coursemodule"
[courseId]="courseId">
</core-format-text>

View File

@ -61,7 +61,7 @@
</ion-item>
</ion-list>
<div collapsible-footer *ngIf="loaded && attempt && showReviewColumn && attempt.finished" slot="fixed">
<div collapsible-footer appearOnBottom *ngIf="loaded && attempt && showReviewColumn && attempt.finished" slot="fixed">
<div class="list-item-limited-width">
<ion-button class="ion-margin ion-text-wrap" expand="block" (click)="reviewAttempt()">
<ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon>

View File

@ -149,7 +149,7 @@
</ion-button>
</ion-card>
<div collapsible-footer *ngIf="!quizAborted && showSummary && summaryQuestions.length && loaded" slot="fixed"
<div collapsible-footer appearOnBottom *ngIf="!quizAborted && showSummary && summaryQuestions.length && loaded" slot="fixed"
class="list-item-limited-width">
<ion-button *ngIf="preventSubmitMessages.length" expand="block" class="ion-margin ion-text-wrap" [href]="moduleUrl" core-link
[showBrowserWarning]="false">

View File

@ -105,7 +105,7 @@
</div>
</div>
<div collapsible-footer *ngIf="loaded && numPages > 1" slot="fixed">
<div collapsible-footer appearOnBottom *ngIf="loaded && numPages > 1" slot="fixed">
<ion-row class="ion-justify-content-between ion-align-items-center ion-no-padding ion-wrap">
<ion-col class="ion-text-start ion-no-padding core-navigation-arrow" size="auto" *ngIf="!showAll">
<ion-button [disabled]="previousPage < 0" fill="clear" [attr.aria-label]="'core.previous' | translate"

View File

@ -28,7 +28,7 @@
<p *ngIf="!src && errorMessage">{{ errorMessage | translate }}</p>
</core-loading>
<core-navigation-bar collapsible-footer *ngIf="loaded && navigationItems.length > 1" [items]="navigationItems"
<core-navigation-bar collapsible-footer appearOnBottom *ngIf="loaded && navigationItems.length > 1" [items]="navigationItems"
(action)="loadSco($event)" slot="fixed">
</core-navigation-bar>
</ion-content>

View File

@ -58,8 +58,8 @@
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'addon.mod_workshop.conclusion' | translate }}</h2>
<core-format-text [collapsible-item]="120" [component]="component" [componentId]="module.id"
[text]="workshop!.conclusion" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
<core-format-text collapsible-item [component]="component" [componentId]="module.id" [text]="workshop!.conclusion"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
</ion-label>
</ion-item>
@ -91,8 +91,8 @@
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'addon.mod_workshop.areainstructauthors' | translate }}</h2>
<core-format-text [collapsible-item]="120" [component]="component" [componentId]="module.id"
[text]="workshop!.instructauthors" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
<core-format-text collapsible-item [component]="component" [componentId]="module.id" [text]="workshop!.instructauthors"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
</ion-label>
</ion-item>
@ -141,7 +141,7 @@
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'addon.mod_workshop.areainstructreviewers' | translate }}</h2>
<core-format-text [collapsible-item]="120" [component]="component" [componentId]="module.id"
<core-format-text collapsible-item [component]="component" [componentId]="module.id"
[text]="workshop!.instructreviewers" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId">
</core-format-text>

View File

@ -64,7 +64,7 @@
<ion-item class="ion-text-wrap">
<ion-label>
<core-format-text [text]="notification.mobiletext | coreCreateLinks" contextLevel="system" [contextInstanceId]="0"
[collapsible-item]="120">
collapsible-item>
</core-format-text>
</ion-label>
</ion-item>

View File

@ -14,7 +14,7 @@
import { AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { IonRouterOutlet } from '@ionic/angular';
import { BackButtonEvent } from '@ionic/core';
import { BackButtonEvent, ScrollDetail } from '@ionic/core';
import { CoreLang } from '@services/lang';
import { CoreLoginHelper } from '@features/login/services/login-helper';
@ -89,6 +89,12 @@ export class AppComponent implements OnInit, AfterViewInit {
}
});
// Listen to scroll to add style when scroll is not 0.
win.addEventListener('ionScroll', ({ detail, target }: CustomEvent<ScrollDetail>) => {
const header = (target as HTMLElement).closest('.ion-page')?.querySelector('ion-header');
header?.classList.toggle('core-header-shadow', detail.scrollTop > 0);
});
// Listen for session expired events.
CoreEvents.on(CoreEvents.SESSION_EXPIRED, (data) => {
CoreLoginHelper.sessionExpired(data);

View File

@ -42,9 +42,10 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
protected initialPaddingBottom = '0px';
protected previousTop = 0;
protected previousHeight = 0;
protected endAnimationTimeout?: number;
protected content?: HTMLIonContentElement | null;
protected loadingChangedListener?: CoreEventObserver;
protected contentScrollListener?: EventListener;
protected endContentScrollListener?: EventListener;
constructor(el: ElementRef, protected ionContent: IonContent) {
this.element = el.nativeElement;
@ -105,7 +106,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
const scroll = await this.content.getScrollElement();
this.content.scrollEvents = true;
this.content.addEventListener('ionScroll', (e: CustomEvent<ScrollDetail>): void => {
this.content.addEventListener('ionScroll', this.contentScrollListener = (e: CustomEvent<ScrollDetail>): void => {
if (!this.content) {
return;
}
@ -113,6 +114,23 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
this.onScroll(e.detail, scroll);
});
this.content.addEventListener('ionScrollEnd', this.endContentScrollListener = (): void => {
if (!this.content) {
return;
}
const height = this.previousHeight;
const collapsed = height <= this.finalHeight;
const expanded = height >= this.initialHeight;
if (!collapsed && !expanded) {
// Finish opening or closing the bar.
const newHeight = (height - this.finalHeight) < (this.initialHeight - this.finalHeight) / 2
? this.finalHeight
: this.initialHeight;
this.setBarHeight(newHeight); }
});
}
/**
@ -154,34 +172,12 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
* @param height The new bar height.
*/
protected setBarHeight(height: number): void {
if (this.endAnimationTimeout) {
clearTimeout(this.endAnimationTimeout);
}
const collapsed = height <= this.finalHeight;
const expanded = height >= this.initialHeight;
this.element.classList.toggle('footer-collapsed', collapsed);
this.element.classList.toggle('footer-expanded', expanded);
this.content?.style.setProperty('--core-collapsible-footer-height', height + 'px');
this.previousHeight = height;
if (!collapsed && !expanded) {
// Finish opening or closing the bar.
this.endAnimationTimeout = window.setTimeout(() => this.endAnimation(height), 500);
}
}
/**
* End of animation when not scrolling.
*
* @param height Last height used.
*/
protected endAnimation(height: number): void {
const newHeight = (height - this.finalHeight) < (this.initialHeight - this.finalHeight) / 2
? this.finalHeight
: this.initialHeight;
this.setBarHeight(newHeight);
}
/**
@ -213,6 +209,13 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
*/
async ngOnDestroy(): Promise<void> {
this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom);
if (this.content && this.contentScrollListener) {
this.content.removeEventListener('ionScroll', this.contentScrollListener);
}
if (this.content && this.endContentScrollListener) {
this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
}
}
}

View File

@ -62,11 +62,12 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
protected expandedFontStyles?: Partial<CSSStyleDeclaration>;
protected content?: HTMLIonContentElement;
protected contentScrollListener?: EventListener;
protected endContentScrollListener?: EventListener;
protected floatingTitle?: HTMLElement;
protected scrollingHeight?: number;
protected subscriptions: Subscription[] = [];
protected enabled = true;
protected endAnimationTimeout?: number;
protected isWithinContent = false;
constructor(protected el: ElementRef) {}
@ -107,6 +108,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
if (this.content && this.contentScrollListener) {
this.content.removeEventListener('ionScroll', this.contentScrollListener);
}
if (this.content && this.endContentScrollListener) {
this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
}
}
/**
@ -280,13 +284,19 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
* @param content Content element.
*/
protected updateContent(content?: HTMLIonContentElement | null): void {
if (this.content && this.contentScrollListener) {
if (this.content) {
if (this.contentScrollListener) {
this.content.removeEventListener('ionScroll', this.contentScrollListener);
delete this.content;
delete this.contentScrollListener;
}
if (this.endContentScrollListener) {
this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
delete this.endContentScrollListener;
}
delete this.content;
}
content && this.trackContentScroll(content);
}
@ -346,24 +356,29 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
throw new Error('[collapsible-header] Couldn\'t set up scrolling');
}
page.classList.toggle('is-within-content', content.contains(expandedHeader));
this.isWithinContent = content.contains(expandedHeader);
page.classList.toggle('is-within-content', this.isWithinContent);
this.setEnabled(this.enabled);
Object
.entries(expandedFontStyles)
.forEach(([property, value]) => floatingTitle.style.setProperty(property, value as string));
this.content.scrollEvents = true;
this.content.addEventListener('ionScroll', this.contentScrollListener = ({ target }: CustomEvent<ScrollDetail>): void => {
if (target !== this.content || !this.enabled) {
return;
}
if (this.endAnimationTimeout) {
clearTimeout(this.endAnimationTimeout);
}
const scrollableHeight = contentScroll.scrollHeight - contentScroll.clientHeight;
const frozen = scrollableHeight <= scrollingHeight;
let frozen = false;
if (this.isWithinContent) {
frozen = scrollableHeight <= scrollingHeight;
} else {
const collapsedHeight = expandedHeaderHeight - (expandedHeader.clientHeight ?? 0);
frozen = scrollableHeight + collapsedHeight <= 2 * expandedHeaderHeight;
}
const progress = frozen
? 0
: CoreMath.clamp(contentScroll.scrollTop / scrollingHeight, 0, 1);
@ -375,29 +390,21 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
Object
.entries(progress > .5 ? collapsedFontStyles : expandedFontStyles)
.forEach(([property, value]) => floatingTitle.style.setProperty(property, value as string));
if (progress > 0 && progress < 1) {
// Finish opening or closing the bar.
this.endAnimationTimeout = window.setTimeout(() => this.endAnimation(progress, contentScroll.scrollTop), 500);
}
});
}
/**
* End of animation when stop scrolling.
*
* @param progress Progress.
* @param scrollTop Current ScrollTop position.
*/
protected endAnimation(progress: number, scrollTop: number): void {
if(!this.page) {
this.content.addEventListener(
'ionScrollEnd',
this.endContentScrollListener = ({ target }: CustomEvent<ScrollDetail>): void => {
if (target !== this.content || !this.enabled) {
return;
}
const progress = parseFloat(page.style.getPropertyValue('--collapsible-header-progress'));
const scrollTop = contentScroll.scrollTop;
const collapse = progress > 0.5;
this.page.style.setProperty('--collapsible-header-progress', collapse ? '1' : '0');
this.page.classList.toggle('is-collapsed', collapse);
page.style.setProperty('--collapsible-header-progress', collapse ? '1' : '0');
page.classList.toggle('is-collapsed', collapse);
if (collapse && this.scrollingHeight && this.scrollingHeight > 0 && scrollTop < this.scrollingHeight) {
this.content?.scrollToPoint(null, this.scrollingHeight);
@ -406,6 +413,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
if (!collapse && this.scrollingHeight && this.scrollingHeight > 0 && scrollTop > 0) {
this.content?.scrollToPoint(null, 0);
}
},
);
}
}

View File

@ -20,8 +20,8 @@ import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreFormatTextDirective } from './format-text';
const defaultMaxHeight = 64;
const buttonHeight = 44;
const defaultMaxHeight = 80;
const minMaxHeight = 56;
/**
* Directive to make an element collapsible.
@ -59,6 +59,10 @@ export class CoreCollapsibleItemDirective implements OnInit {
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
if (this.height === null) {
return;
}
if (typeof this.height === 'string') {
this.maxHeight = this.height === ''
? defaultMaxHeight
@ -66,7 +70,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
} else {
this.maxHeight = this.height;
}
this.maxHeight = this.maxHeight < defaultMaxHeight ? defaultMaxHeight : this.maxHeight;
this.maxHeight = this.maxHeight < minMaxHeight ? defaultMaxHeight : this.maxHeight;
if (!this.maxHeight) {
// Do not collapse.
@ -141,7 +145,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
this.element.classList.toggle('collapsible-enabled', enable);
if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) {
this.setMaxHeight(!enable || this.expanded? undefined : this.maxHeight);
this.setHeight(!enable || this.expanded ? undefined : this.maxHeight);
return;
}
@ -168,15 +172,15 @@ export class CoreCollapsibleItemDirective implements OnInit {
/**
* Set max height to element.
*
* @param maxHeight Max height if collapsed or undefined if expanded.
* @param height Max height if collapsed or undefined if expanded.
*/
protected setMaxHeight(maxHeight?: number): void {
if (maxHeight) {
this.element.style.setProperty('--max-height', maxHeight + buttonHeight + 'px');
protected setHeight(height?: number): void {
if (height) {
this.element.style.setProperty('--collapsible-height', height + 'px');
} else if (this.expandedHeight) {
this.element.style.setProperty('--max-height', this.expandedHeight + 'px');
this.element.style.setProperty('--collapsible-height', this.expandedHeight + 'px');
} else {
this.element.style.removeProperty('--max-height');
this.element.style.removeProperty('--collapsible-height');
}
}
@ -192,7 +196,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
}
this.expanded = expand;
this.element.classList.toggle('collapsible-collapsed', !expand);
this.setMaxHeight(!expand? this.maxHeight: undefined);
this.setHeight(!expand ? this.maxHeight: undefined);
const toggleButton = this.element.querySelector('ion-button.collapsible-toggle');
const toggleText = toggleButton?.querySelector('.collapsible-toggle-text');

View File

@ -0,0 +1,46 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, ElementRef, OnInit } from '@angular/core';
/**
* Directive to enabled scroll events on ALL scrollable ion-content.
*
* Example usage:
*
* <ion-content>
*/
@Directive({
selector: 'ion-content',
})
export class CoreContentDirective implements OnInit {
protected element: HTMLIonContentElement;
constructor(el: ElementRef) {
this.element = el.nativeElement;
}
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
if (this.element.classList.contains('disable-scroll-y')) {
return;
}
this.element.scrollEvents = true;
}
}

View File

@ -31,6 +31,7 @@ import { CoreCollapsibleHeaderDirective } from './collapsible-header';
import { CoreSwipeNavigationDirective } from './swipe-navigation';
import { CoreCollapsibleItemDirective } from './collapsible-item';
import { CoreCollapsibleFooterDirective } from './collapsible-footer';
import { CoreContentDirective } from './content';
@NgModule({
declarations: [
@ -51,6 +52,7 @@ import { CoreCollapsibleFooterDirective } from './collapsible-footer';
CoreSwipeNavigationDirective,
CoreCollapsibleItemDirective,
CoreCollapsibleFooterDirective,
CoreContentDirective,
],
exports: [
CoreAutoFocusDirective,
@ -70,6 +72,7 @@ import { CoreCollapsibleFooterDirective } from './collapsible-footer';
CoreSwipeNavigationDirective,
CoreCollapsibleItemDirective,
CoreCollapsibleFooterDirective,
CoreContentDirective,
],
})
export class CoreDirectivesModule {}

View File

@ -24,7 +24,7 @@
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
</div>
<div collapsible-footer *ngIf="displayCourseIndex && (previousSection || nextSection)" slot="fixed">
<div collapsible-footer appearOnBottom *ngIf="displayCourseIndex && (previousSection || nextSection)" slot="fixed">
<div class="core-course-section-nav-buttons safe-area-padding-horizontal list-item-limited-width">
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" expand="block"
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">

View File

@ -1,7 +1,7 @@
<ion-card *ngIf="description">
<ion-item class="ion-text-wrap">
<ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" [collapsible-item]="120"
<core-format-text [text]="description" [component]="component" [componentId]="componentId" collapsible-item
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
</core-format-text>
</ion-label>

View File

@ -46,7 +46,7 @@
<ion-item class="ion-text-wrap" *ngIf="description">
<ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module.id" [courseId]="courseId" [collapsible-item]="expandDescription ? null : 120">
[contextInstanceId]="module.id" [courseId]="courseId" [collapsible-item]="expandDescription ? null : ''">
</core-format-text>
</ion-label>
</ion-item>

View File

@ -46,6 +46,23 @@
padding-top: 8px;
}
core-course-module-completion ::ng-deep ion-button {
min-height: 28px;
margin: 0;
font-size: 12px;
text-transform: none;
font-weight: normal;
ion-icon {
font-size: 16px;
min-width: 16px;
@include margin(0, 8px, 0, 0);
}
ion-label {
white-space: normal !important;
}
}
}

View File

@ -49,7 +49,7 @@
{{ 'core.description' | translate}}
</p>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module.id" [courseId]="courseId" [collapsible-item]="120">
[contextInstanceId]="module.id" [courseId]="courseId" collapsible-item>
</core-format-text>
</ion-label>
</ion-item>
@ -169,7 +169,7 @@
<ion-label>
<p class="item-heading">{{ 'core.grades.feedback' | translate}}</p>
<p>
<core-format-text [collapsible-item]="120" [text]="grade.feedback" contextLevel="course"
<core-format-text collapsible-item [text]="grade.feedback" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</p>

View File

@ -66,7 +66,7 @@
class="ion-text-wrap core-course-module-handler core-course-module-info {{module.handlerData.class}}" [ngClass]="{
'item-dimmed': module.visible === 0 || module.uservisible === false
}">
<ion-label collapsible-item>
<ion-label [collapsible-item]="64">
<core-format-text class="core-module-description" *ngIf="module.description" [text]="module.description"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
</core-format-text>

View File

@ -72,8 +72,7 @@
<p class="item-heading">
{{'core.summary' | translate}}
</p>
<core-format-text [text]="course.summary" [collapsible-item]="120" contextLevel="course"
[contextInstanceId]="course.id">
<core-format-text [text]="course.summary" collapsible-item contextLevel="course" [contextInstanceId]="course.id">
</core-format-text>
</ion-label>
</ion-item>
@ -105,7 +104,7 @@
</core-format-text>
</span><span class="core-customfieldseparator">: </span>
<span class="core-customfieldvalue">
<core-format-text [text]="field.value" [collapsible-item]="120" contextLevel="course"
<core-format-text [text]="field.value" collapsible-item contextLevel="course"
[contextInstanceId]="course.id">
</core-format-text>
</span>

View File

@ -34,7 +34,7 @@
</core-format-text>
</p>
<p *ngIf="currentCategory.description">
<core-format-text [text]="currentCategory.description" [collapsible-item]="120" contextLevel="coursecat"
<core-format-text [text]="currentCategory.description" collapsible-item contextLevel="coursecat"
[contextInstanceId]="currentCategory.id"></core-format-text>
</p>
</ion-label>

View File

@ -55,7 +55,7 @@
</td>
<td *ngIf="column.name === 'feedback' && row.feedback !== undefined"
class="ion-text-start core-grades-table-feedback" [class.ion-hide-md-down]="column.hiddenPhone">
<core-format-text [collapsible-item]="120" [text]="row.feedback" contextLevel="course"
<core-format-text collapsible-item [text]="row.feedback" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</td>
@ -124,7 +124,7 @@
<ion-label>
<h2>{{ 'core.grades.feedback' | translate}}</h2>
<p>
<core-format-text [collapsible-item]="120" [text]="row.feedback" contextLevel="course"
<core-format-text collapsible-item [text]="row.feedback" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</p>

View File

@ -13,6 +13,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<core-loading [hideUntil]="siteLogoLoaded && handlersLoaded">
<ion-list>
<ion-item button class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo" lines="full" detail="false" [href]="siteUrl"
core-link auto-login="yes">
@ -66,6 +67,7 @@
</ion-label>
</ion-item>
</ion-list>
</core-loading>
</ion-content>
<ion-footer>
<ion-item *ngIf="displaySwitchAccount" button lines="full" (click)="switchAccounts($event)" detail="true" class="ion-text-wrap">

View File

@ -49,7 +49,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
siteUrl?: string;
handlers: CoreUserProfileHandlerData[] = [];
handlersLoaded = false;
loaded = false;
user?: CoreUserProfile;
displaySwitchAccount = true;
removeAccountOnLogout = false;
@ -68,8 +67,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
this.displaySwitchAccount = !currentSite.isFeatureDisabled('NoDelegate_SwitchAccount');
this.removeAccountOnLogout = !!CoreConstants.CONFIG.removeaccountonlogout;
this.loaded = true;
this.loadSiteLogo(currentSite);
// Load the handlers.

View File

@ -8,6 +8,9 @@
--collapsible-header-floating-title-width: 0px;
--collapsible-header-floating-title-x-delta: 0px;
--collapsible-header-floating-title-width-delta: 0px;
ion-header.core-header-shadow {
--core-header-shadow: none;
}
.collapsible-header-expanded {
overflow: hidden;
@ -24,7 +27,9 @@
&:not(.is-collapsed) .collapsible-header-collapsed {
--core-header-toolbar-border-width: 0;
--core-header-toolbar-background: transparent;
ion-toolbar {
--background: transparent;
}
h1 {
opacity: 0;
@ -32,6 +37,10 @@
}
&.is-active.is-collapsed.is-within-content ion-header.core-header-shadow {
--core-header-shadow: var(--drop-shadow-bottom, none);
}
&.is-active {
.collapsible-header-expanded {

View File

@ -1,12 +1,14 @@
.collapsible-item {
--display-toggle: none;
--max-height: none;
--collapsible-height: none;
--toggle-size: 24px;
--gradient-size: 44px;
&.collapsible-loading-height {
display: block !important;
height: auto !important;
--max-height: none !important;
--collapsible-height: auto !important;
--display-toggle: none !important;
}
@ -15,12 +17,12 @@
}
@include media-breakpoint-down(sm) {
&.collapsible-enabled {
&.collapsible-enabled:not(.collapsible-loading-height) {
position: relative;
padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit.
padding-bottom: var(--toggle-size); // So the Show less button can fit.
--display-toggle: block;
@include core-transition(height max-height, 500ms);
height: calc(var(--max-height, auto));
@include core-transition(height, 300ms);
height: calc(var(--collapsible-height, auto) + var(--toggle-size));
.collapsible-toggle {
position: absolute;
@ -28,12 +30,12 @@
text-align: center;
z-index: 7;
text-transform: none;
font-size: 14px;
font-size: 11px;
font-weight: normal;
background-color: var(--collapsible-toggle-background);
color: var(--collapsible-toggle-text);
min-height: var(--a11y-min-target-size);
min-width: var(--a11y-min-target-size);
min-height: var(--toggle-size);
min-width: var(--toggle-size);
--border-radius: var(--huge-radius);
border-radius: var(--border-radius);
--padding-start: 0px;
@ -41,8 +43,8 @@
margin: 0px;
.collapsible-toggle-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
width: var(--toggle-size);
height: var(--toggle-size);
background-position: center;
background-repeat: no-repeat;
@ -61,7 +63,8 @@
&.collapsible-collapsed {
overflow: hidden;
min-height: calc(var(--collapsible-min-button-height) + 12px);
min-height: calc(var(--toggle-size) + 12px);
height: var(--collapsible-height, auto);
.collapsible-toggle-arrow {
transform: rotate(90deg);
@ -69,11 +72,11 @@
&:before {
content: '';
height: 100%;
height: 60px;
position: absolute;
@include position(null, 0, 0, 0);
background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 56px), rgba(var(--background-gradient-rgb), 1) calc(100% - 5px));
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 56px), rgba(var(--background-gradient-rgb), 1) calc(100% - 5px));
background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px));
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px));
z-index: 6;
}
}

View File

@ -57,6 +57,7 @@ ion-item.addon-message {
}
&:hover {
-webkit-filter: drop-shadow(2px 2px 2px rgba(0,0,0,.3));
filter: drop-shadow(2px 2px 2px rgba(0,0,0,.3));
}

View File

@ -70,10 +70,22 @@ core-format-text {
display: block;
}
&.collapsible-enabled {
.core-format-text-content {
display: block;
max-height: none;
}
&.collapsible-collapsed .core-format-text-content {
overflow: hidden;
height: var(--collapsible-height);
@include core-transition(height, 300ms);
}
}
@if ($core-format-text-never-shorten) {
&.collapsible-enabled {
--display-toggle: none !important;
--max-height: none !important;
--collapsible-height: auto !important;
.collapsible-toggle {
display: none !important;
@ -85,6 +97,12 @@ core-format-text {
}
}
}
&.collapsible-item.inline {
display: inline-block;
&.collapsible-enabled .core-format-text-content {
display: inline-block;
}
}
.core-adapted-img-container {
position: relative;

View File

@ -161,6 +161,7 @@ ion-header {
ion-back-button,
.in-toolbar.button-clear {
--color: var(--core-header-toolbar-color);
--background: var(--core-header-toolbar-background);
--ion-toolbar-color: var(--core-header-toolbar-color);
--border-radius: var(--huge-radius);
}
@ -170,7 +171,7 @@ ion-header {
.button.button-clear,
.button.button-solid {
--background: transparent;
--background: var(--core-header-toolbar-background);
--color: var(--core-header-toolbar-color);
--primary: var(--core-header-toolbar-color);
}
@ -509,7 +510,6 @@ ion-toast {
// Ionic list.
ion-list {
padding: 0 !important;
--ion-item-background: transparent;
}
// Safe areas
@ -739,7 +739,7 @@ body.core-iframe-fullscreen ion-router-outlet {
ion-label {
white-space: normal !important;
}
ion-item > ion-icon {
ion-item > ion-icon[slot] {
color: var(--color-shade);
@include margin-horizontal(null, 16px);
}
@ -752,7 +752,7 @@ body.core-iframe-fullscreen ion-router-outlet {
--border-width: 0 0 3px 0;
--border-color: var(--color-base);
--inner-border-width: 0px;
ion-icon {
> ion-icon[slot] {
color: var(--color-base);
}
}
@ -835,8 +835,7 @@ ion-toolbar h1 .core-bar-button-image img {
// Action sheet.
.md ion-action-sheet {
.action-sheet-group-cancel {
-webkit-filter: drop-shadow(0px 3px 6px rgba(var(--drop-shadow)));
filter: drop-shadow(0px 3px 6px rgba(var(--drop-shadow)));
box-shadow: var(--drop-shadow-top, none);
}
.action-sheet-title {
@ -1347,7 +1346,10 @@ ion-item.item.divider {
ion-label h2.big {
font-size: var(--item-divider-font-size-big);
}
}
ion-item-divider.item,
ion-item.item {
.expandable-status-icon {
font-size: 18px;
@include core-transition(transform, 200ms);
@ -1443,8 +1445,7 @@ ion-grid.core-no-grid > ion-row {
margin-bottom: 8px;
}
filter: var(--scroll-shadow-top, none);
-webkit-filter: var(--scroll-shadow-top, none);
box-shadow: var(--drop-shadow-top, none);
width: 100%;
bottom: 0;
z-index: 3;
@ -1472,6 +1473,7 @@ ion-grid.core-no-grid > ion-row {
ion-header.no-title {
--core-header-toolbar-border-width: 0;
--core-header-toolbar-background: transparent;
--core-header-shadow: none !important;
ion-toolbar .button.button-clear,
ion-toolbar .button.button-solid {

View File

@ -39,7 +39,11 @@
--subdued-text-color: var(--medium);
--stroke: var(--gray-700);
--contrast-background: black;
--contrast-background: var(--gray-900);
--drop-shadow-color: 0, 0, 0, 1;
--drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color));
--drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));
--ion-card-color: var(--text-color);
--ion-card-background: var(--ion-item-background);
@ -73,7 +77,7 @@
--core-header-toolbar-color: var(--text-color);
--core-header-toolbar-border-color: var(--stroke);
--core-tabs-background: var(--gray-800);
--core-tabs-background: var(--gray-900);
--core-tab-background: var(--core-tabs-background);
--core-tab-color: var(--subdued-text-color);
--core-tab-border-color: var(--gray-200);
@ -104,7 +108,7 @@
--core-combobox-color: var(--text-color);
--core-combobox-border-color: var(--core-input-stroke);
--collapsible-toggle-background: var(--light);
--collapsible-toggle-text: var(--medium);
--background-gradient-rgb: #{$ion-item-background-dark-rgb};

View File

@ -85,6 +85,10 @@
--contrast-background: white;
--drop-shadow-color: 0, 0, 0, 0.5;
--drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color));
--drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));
--ion-text-color: var(--text-color);
--ion-text-color-rgb: #{$text-color-rgb};
--subdued-text-color: var(--medium);
@ -138,11 +142,13 @@
--core-header-toolbar-border-color: var(--stroke);
--core-header-toolbar-color: var(--text-color);
--core-header-toolbar-height: 48px;
html.ios {
--core-header-toolbar-height: 48px;
}
--core-header-shadow: none;
ion-header ion-toolbar {
ion-header {
box-shadow: var(--core-header-shadow, none);
transition: box-shadow 0.5s;
ion-toolbar {
--color: var(--core-header-toolbar-color);
--background: var(--core-header-toolbar-background);
--border-width: 0 0 var(--core-header-toolbar-border-width) 0;
@ -158,6 +164,12 @@
--color: var(--core-header-toolbar-color);
}
}
}
ion-header.core-header-shadow {
--core-header-shadow: var(--drop-shadow-bottom, none);
}
ion-header::after {
display: none;
@ -300,9 +312,8 @@
--selected-item-color: var(--primary);
--selected-item-border-width: 5px;
--collapsible-toggle-background: var(--light);
--collapsible-min-button-height: 44px;
--collapsible-toggle-text: var(--text-color);
--collapsible-toggle-background: transparent;
--collapsible-toggle-text: var(--medium);
--background-gradient-rgb: #{$ion-item-background-rgb};
@ -343,10 +354,6 @@
--addon-forum-border-color: var(--stroke);
--addon-forum-highlight-color: var(--light);
--drop-shadow: 0, 0, 0, 0.5;
--scroll-shadow-bottom: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
--scroll-shadow-top: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
--core-question-correct-color: var(--success-shade);
--core-question-correct-color-bg: var(--success-tint);
--core-question-incorrect-color: var(--danger);