MOBILE-3814 format-text: collapsible item directive replaces maxHeight

main
Pau Ferrer Ocaña 2022-03-08 11:25:37 +01:00
parent 09cd1af733
commit 112f00bcb5
20 changed files with 166 additions and 288 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
protected toggleExpandEnabled = false; protected toggleExpandEnabled = false;
protected expanded = false; protected expanded = false;
protected maxHeight = defaultMaxHeight; protected maxHeight = defaultMaxHeight;
protected expandedHeight = 0;
protected loadingChangedListener?: CoreEventObserver; protected loadingChangedListener?: CoreEventObserver;
constructor(el: ElementRef<HTMLElement>) { constructor(el: ElementRef<HTMLElement>) {
@ -72,9 +73,10 @@ export class CoreCollapsibleItemDirective implements OnInit {
return; return;
} }
this.element.classList.add('collapsible-item');
// Calculate the height now. // Calculate the height now.
await this.calculateHeight(); await this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong.
// Recalculate the height if a parent core-loading displays the content. // Recalculate the height if a parent core-loading displays the content.
this.loadingChangedListener = this.loadingChangedListener =
@ -82,7 +84,6 @@ export class CoreCollapsibleItemDirective implements OnInit {
if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) { if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) {
// The element is inside the loading, re-calculate the height. // The element is inside the loading, re-calculate the height.
await this.calculateHeight(); await this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200);
} }
}); });
} }
@ -93,9 +94,15 @@ export class CoreCollapsibleItemDirective implements OnInit {
* @param element Element. * @param element Element.
*/ */
protected async waitFormatTextsRendered(element: Element): Promise<void> { protected async waitFormatTextsRendered(element: Element): Promise<void> {
const formatTexts = Array let formatTextElements: HTMLElement[] = [];
.from(element.querySelectorAll('core-format-text'))
.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); if (this.element.tagName == 'CORE-FORMAT-TEXT') {
formatTextElements = [this.element];
} else {
formatTextElements = Array.from(element.querySelectorAll('core-format-text'));
}
const formatTexts = formatTextElements.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective));
await Promise.all(formatTexts.map(formatText => formatText?.rendered())); await Promise.all(formatTexts.map(formatText => formatText?.rendered()));
} }
@ -103,22 +110,25 @@ export class CoreCollapsibleItemDirective implements OnInit {
/** /**
* Calculate the height and check if we need to display show more or not. * Calculate the height and check if we need to display show more or not.
*/ */
protected async calculateHeight(): Promise<void> { protected async calculateHeight(retries = 3): Promise<void> {
await this.waitFormatTextsRendered(this.element);
// Remove max-height (if any) to calculate the real height. // Remove max-height (if any) to calculate the real height.
const initialMaxHeight = this.element.style.maxHeight; this.element.classList.add('collapsible-loading-height');
this.element.style.maxHeight = 'none';
await this.waitFormatTextsRendered(this.element);
await CoreUtils.nextTick(); await CoreUtils.nextTick();
const height = CoreDomUtils.getElementHeight(this.element) || 0; this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0;
// Restore the max height now. // Restore the max height now.
this.element.style.maxHeight = initialMaxHeight; this.element.classList.remove('collapsible-loading-height');
// If cannot calculate height, shorten always. // If cannot calculate height, shorten always.
this.setExpandButtonEnabled(!height || height >= this.maxHeight); this.setExpandButtonEnabled(!this.expandedHeight || this.expandedHeight >= this.maxHeight);
if (this.expandedHeight == 0 && retries > 0) {
setTimeout(() => this.calculateHeight(retries - 1), 200);
}
} }
/** /**
@ -163,8 +173,11 @@ export class CoreCollapsibleItemDirective implements OnInit {
protected setMaxHeight(maxHeight?: number): void { protected setMaxHeight(maxHeight?: number): void {
if (maxHeight) { if (maxHeight) {
this.element.style.setProperty('--max-height', maxHeight + buttonHeight + 'px'); this.element.style.setProperty('--max-height', maxHeight + buttonHeight + 'px');
} else if (this.expandedHeight) {
this.element.style.setProperty('--max-height', this.expandedHeight + 'px');
} else { } else {
this.element.style.removeProperty('--max-height'); this.element.style.removeProperty('--max-height');
} }
} }
@ -195,7 +208,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
* *
* @param e Click event. * @param e Click event.
*/ */
protected elementClicked(e: MouseEvent): void { elementClicked(e: MouseEvent): void {
if (e.defaultPrevented) { if (e.defaultPrevented) {
// Ignore it if the event was prevented by some other listener. // Ignore it if the event was prevented by some other listener.
return; return;

View File

@ -22,10 +22,10 @@ import {
SimpleChange, SimpleChange,
Optional, Optional,
ViewContainerRef, ViewContainerRef,
ViewChild,
} from '@angular/core'; } from '@angular/core';
import { IonContent } from '@ionic/angular'; import { IonContent } from '@ionic/angular';
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreIframeUtils, CoreIframeUtilsProvider } from '@services/utils/iframe'; import { CoreIframeUtils, CoreIframeUtilsProvider } from '@services/utils/iframe';
@ -40,6 +40,7 @@ import { CoreFilterDelegate } from '@features/filter/services/filter-delegate';
import { CoreFilterHelper } from '@features/filter/services/filter-helper'; import { CoreFilterHelper } from '@features/filter/services/filter-helper';
import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreSubscriptions } from '@singletons/subscriptions';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreCollapsibleItemDirective } from './collapsible-item';
/** /**
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
@ -55,6 +56,8 @@ import { CoreComponentsRegistry } from '@singletons/components-registry';
}) })
export class CoreFormatTextDirective implements OnChanges { export class CoreFormatTextDirective implements OnChanges {
@ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective;
@Input() text?: string; // The text to format. @Input() text?: string; // The text to format.
@Input() siteId?: string; // Site ID to use. @Input() siteId?: string; // Site ID to use.
@Input() component?: string; // Component for CoreExternalContentDirective. @Input() component?: string; // Component for CoreExternalContentDirective.
@ -73,23 +76,18 @@ export class CoreFormatTextDirective implements OnChanges {
@Input() hideIfEmpty = false; // If true, the tag will contain nothing if text is empty. @Input() hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
@Input() fullOnClick?: boolean | string; // @deprecated on 4.0 Won't do anything. @Input() fullOnClick?: boolean | string; // @deprecated on 4.0 Won't do anything.
@Input() fullTitle?: string; // @deprecated on 4.0 Won't do anything.. @Input() fullTitle?: string; // @deprecated on 4.0 Won't do anything.
/** /**
* Max height in pixels to render the content box. It should be 50 at least to make sense. * Max height in pixels to render the content box. It should be 50 at least to make sense.
* Using this parameter will force display: block to calculate height better.
* If you want to avoid this use class="inline" at the same time to use display: inline-block.
*/ */
@Input() maxHeight?: number; @Input() maxHeight?: number; // @deprecated on 4.0 Use collapsible-item directive instead.
@Output() afterRender: EventEmitter<void>; // Called when the data is rendered. @Output() afterRender: EventEmitter<void>; // Called when the data is rendered.
@Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked. @Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked.
protected element: HTMLElement; protected element: HTMLElement;
protected expanded = false;
protected loadingChangedListener?: CoreEventObserver;
protected emptyText = ''; protected emptyText = '';
protected contentSpan: HTMLElement; protected contentSpan: HTMLElement;
protected toggleExpandEnabled = false;
constructor( constructor(
element: ElementRef, element: ElementRef,
@ -122,8 +120,6 @@ export class CoreFormatTextDirective implements OnChanges {
*/ */
ngOnChanges(changes: { [name: string]: SimpleChange }): void { ngOnChanges(changes: { [name: string]: SimpleChange }): void {
if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) { if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) {
this.setExpandButtonEnabled(false);
this.formatAndRenderContents(); this.formatAndRenderContents();
} }
} }
@ -269,101 +265,6 @@ export class CoreFormatTextDirective implements OnChanges {
}); });
} }
/**
* Calculate the height and check if we need to display show more or not.
*/
protected async calculateHeight(): Promise<void> {
// @todo: Work on calculate this height better.
if (!this.maxHeight) {
return;
}
await this.rendered();
// Remove max-height (if any) to calculate the real height.
const initialMaxHeight = this.element.style.maxHeight;
this.element.style.maxHeight = 'none';
await CoreUtils.nextTick();
const height = this.getElementHeight(this.element);
// Restore the max height now.
this.element.style.maxHeight = initialMaxHeight;
// If cannot calculate height, shorten always.
this.setExpandButtonEnabled(!height || height >= this.maxHeight);
}
/**
* Set max height to element.
*
* @param maxHeight Max height if collapsed or undefined if expanded.
*/
protected setMaxHeight(maxHeight?: number): void {
if (maxHeight) {
this.element.style.setProperty('--max-height', maxHeight + 'px');
} else {
this.element.style.removeProperty('--max-height');
}
}
/**
* Sets if expand button is enabled or not.
*
* @param enable Wether enable or disable.
*/
protected setExpandButtonEnabled(enable: boolean): void {
this.toggleExpandEnabled = enable;
this.element.classList.toggle('collapsible-enabled', enable);
if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) {
this.setMaxHeight(!enable || this.expanded? undefined : this.maxHeight);
return;
}
// Add expand/collapse buttons
const toggleButton = document.createElement('ion-button');
toggleButton.classList.add('collapsible-toggle');
toggleButton.setAttribute('fill', 'clear');
const toggleText = document.createElement('span');
toggleText.classList.add('collapsible-toggle-text');
toggleText.classList.add('sr-only');
toggleButton.appendChild(toggleText);
const expandArrow = document.createElement('span');
expandArrow.classList.add('collapsible-toggle-arrow');
toggleButton.appendChild(expandArrow);
this.element.appendChild(toggleButton);
this.toggleExpand(this.expanded);
}
/**
* Expand or collapse text.
*
* @param expand Wether expand or collapse text. If undefined, will toggle.
*/
protected toggleExpand(expand?: boolean): void {
if (expand === undefined) {
expand = !this.expanded;
}
this.expanded = expand;
this.element.classList.toggle('collapsible-collapsed', !expand);
this.setMaxHeight(!expand? this.maxHeight: undefined);
const toggleButton = this.element.querySelector('ion-button.collapsible-toggle');
const toggleText = toggleButton?.querySelector('.collapsible-toggle-text');
if (!toggleButton || !toggleText) {
return;
}
toggleText.innerHTML = expand ? Translate.instant('core.showless') : Translate.instant('core.showmore');
toggleButton.setAttribute('aria-expanded', expand ? 'true' : 'false');
}
/** /**
* Listener to call when the element is clicked. * Listener to call when the element is clicked.
* *
@ -385,24 +286,18 @@ export class CoreFormatTextDirective implements OnChanges {
return; return;
} }
if (!this.toggleExpandEnabled) { this.collapsible?.elementClicked(e);
// Nothing to do on click, just stop.
return;
}
e.preventDefault();
e.stopPropagation();
this.toggleExpand();
} }
/** /**
* Finish the rendering, displaying the element again and calling afterRender. * Finish the rendering, displaying the element again and calling afterRender.
*/ */
protected finishRender(): void { protected async finishRender(): Promise<void> {
// Show the element again. // Show the element again.
this.element.classList.remove('core-format-text-loading'); this.element.classList.remove('core-format-text-loading');
await CoreUtils.nextTick();
// Emit the afterRender output. // Emit the afterRender output.
this.afterRender.emit(); this.afterRender.emit();
} }
@ -413,15 +308,12 @@ export class CoreFormatTextDirective implements OnChanges {
protected async formatAndRenderContents(): Promise<void> { protected async formatAndRenderContents(): Promise<void> {
if (!this.text) { if (!this.text) {
this.contentSpan.innerHTML = this.emptyText; // Remove current contents. this.contentSpan.innerHTML = this.emptyText; // Remove current contents.
this.finishRender();
await this.finishRender();
return; return;
} }
// In AOT the inputs and ng-reflect aren't in the DOM sometimes. Add them so styles are applied.
if (this.maxHeight && !this.element.getAttribute('maxHeight')) {
this.element.setAttribute('maxHeight', String(this.maxHeight));
}
if (!this.element.getAttribute('singleLine')) { if (!this.element.getAttribute('singleLine')) {
this.element.setAttribute('singleLine', String(CoreUtils.isTrueOrOne(this.singleLine))); this.element.setAttribute('singleLine', String(CoreUtils.isTrueOrOne(this.singleLine)));
} }
@ -434,36 +326,22 @@ export class CoreFormatTextDirective implements OnChanges {
this.element.classList.add('core-disable-media-adapt'); this.element.classList.add('core-disable-media-adapt');
this.contentSpan.innerHTML = ''; // Remove current contents. this.contentSpan.innerHTML = ''; // Remove current contents.
if (this.maxHeight && result.div.innerHTML != '') {
// Move the children to the current element to be able to calculate the height. // Move the children to the current element to be able to calculate the height.
CoreDomUtils.moveChildren(result.div, this.contentSpan); CoreDomUtils.moveChildren(result.div, this.contentSpan);
// Calculate the height now. await CoreUtils.nextTick();
this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong.
// Add magnifying glasses to images. // Use collapsible-item directive instead.
this.addMagnifyingGlasses(); if (this.maxHeight && !this.collapsible) {
this.collapsible = new CoreCollapsibleItemDirective(new ElementRef(this.element));
if (!this.loadingChangedListener) { this.collapsible.height = this.maxHeight;
// Recalculate the height if a parent core-loading displays the content. this.collapsible.ngOnInit();
this.loadingChangedListener =
CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, (data: CoreEventLoadingChangedData) => {
if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) {
// The format-text is inside the loading, re-calculate the height.
this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200);
}
});
}
} else {
CoreDomUtils.moveChildren(result.div, this.contentSpan);
// Add magnifying glasses to images.
this.addMagnifyingGlasses();
} }
// Add magnifying glasses to images.
this.addMagnifyingGlasses();
if (result.options.filter) { if (result.options.filter) {
// Let filters handle HTML. We do it here because we don't want them to block the render of the text. // Let filters handle HTML. We do it here because we don't want them to block the render of the text.
CoreFilterDelegate.handleHtml( CoreFilterDelegate.handleHtml(
@ -479,7 +357,7 @@ export class CoreFormatTextDirective implements OnChanges {
} }
this.element.classList.remove('core-disable-media-adapt'); this.element.classList.remove('core-disable-media-adapt');
this.finishRender(); await this.finishRender();
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,82 @@
.collapsible-item {
--display-toggle: none;
--max-height: none;
&.collapsible-loading-height {
display: block !important;
height: auto !important;
--max-height: none !important;
--display-toggle: none !important;
}
.collapsible-toggle {
display: var(--display-toggle);
}
@include media-breakpoint-down(sm) {
&.collapsible-enabled {
position: relative;
padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit.
--display-toggle: block;
@include core-transition(height max-height, 500ms);
height: calc(var(--max-height, auto));
.collapsible-toggle {
position: absolute;
@include position (null, 0, 0, null);
text-align: center;
z-index: 7;
text-transform: none;
font-size: 14px;
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);
--border-radius: var(--huge-radius);
border-radius: var(--border-radius);
--padding-start: 0px;
--padding-end: 0px;
margin: 0px;
.collapsible-toggle-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
background-position: center;
background-repeat: no-repeat;
background-size: 14px 14px;
transform: rotate(-90deg);
@include core-transition(transform, 500ms);
@include push-arrow-color(626262, true);
@include darkmode() {
@include push-arrow-color(ffffff, true);
}
}
}
&.collapsible-collapsed {
overflow: hidden;
min-height: calc(var(--collapsible-min-button-height) + 12px);
.collapsible-toggle-arrow {
transform: rotate(90deg);
}
&:before {
content: '';
height: 100%;
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));
z-index: 6;
}
}
}
}
}

View File

@ -48,14 +48,6 @@ core-format-text {
opacity: 0; opacity: 0;
display: inline; display: inline;
} }
.collapsible-toggle {
display: none !important;
}
}
.collapsible-toggle {
display: none;
} }
.core-format-text-content { .core-format-text-content {
@ -68,33 +60,20 @@ core-format-text {
word-wrap: break-word; word-wrap: break-word;
} }
&[maxHeight], &.collapsible-item {
&[ng-reflect-max-height] {
display: block; display: block;
position: relative;
width: 100%;
overflow: hidden;
/* Force display inline */
&.inline {
display: inline-block;
width: auto;
}
// This is to allow clicks in radio/checkbox content. // This is to allow clicks in radio/checkbox content.
&.collapsible-enabled { cursor: pointer;
cursor: pointer; pointer-events: auto;
pointer-events: auto;
@include collapsible-item(); .core-format-text-content {
display: block;
} }
}
@if ($core-format-text-never-shorten) { @if ($core-format-text-never-shorten) {
&[maxHeight], &.collapsible-enabled {
&[ng-reflect-max-height] { --display-toggle: none !important;
&.collapsible-enabled.collapsible-expanded { --max-height: none !important;
max-height: none !important;
.collapsible-toggle { .collapsible-toggle {
display: none !important; display: none !important;

View File

@ -226,78 +226,6 @@
} }
} }
@mixin collapsible-item() {
--display-toggle: none;
.collapsible-toggle {
display: var(--display-toggle);
}
@include media-breakpoint-down(sm) {
&.collapsible-enabled {
position:relative;
padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit.
--display-toggle: block;
.collapsible-toggle {
position: absolute;
@include position (null, 0, 0, null);
text-align: center;
z-index: 7;
text-transform: none;
font-size: 14px;
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);
--border-radius: var(--huge-radius);
border-radius: var(--border-radius);
--padding-start: 0px;
--padding-end: 0px;
margin: 0px;
.collapsible-toggle-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
background-position: center;
background-repeat: no-repeat;
background-size: 14px 14px;
transform: rotate(-90deg);
@include core-transition(transform, 500ms);
@include push-arrow-color(626262, true);
@include darkmode() {
@include push-arrow-color(ffffff, true);
}
}
}
&.collapsible-collapsed {
overflow: hidden;
min-height: calc(var(--collapsible-min-button-height) + 12px);
max-height: calc(var(--max-height, auto));
.collapsible-toggle-arrow {
transform: rotate(90deg);
}
&:before {
content: '';
height: 100%;
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));
z-index: 6;
}
}
}
}
}
// Color mixins. // Color mixins.
@function get_brightness($color) { @function get_brightness($color) {
@return (red($color) + green($color) + blue($color)) / 3; @return (red($color) + green($color) + blue($color)) / 3;

View File

@ -1419,10 +1419,6 @@ ion-grid.core-no-grid > ion-row {
right: 0; right: 0;
} }
[collapsible-item] {
@include collapsible-item();
}
[collapsible-footer] { [collapsible-footer] {
&.footer-collapsed { &.footer-collapsed {
--core-collapsible-footer-height: 0; --core-collapsible-footer-height: 0;

View File

@ -20,6 +20,7 @@
/* Components */ /* Components */
@import "./components/collapsible-header.scss"; @import "./components/collapsible-header.scss";
@import "./components/collapsible-item.scss";
@import "./components/format-text.scss"; @import "./components/format-text.scss";
@import "./components/rubrics.scss"; @import "./components/rubrics.scss";
@import "./components/mod-label.scss"; @import "./components/mod-label.scss";