diff --git a/src/addons/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html b/src/addons/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html index fce2551b7..413cb3751 100644 --- a/src/addons/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html +++ b/src/addons/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html @@ -8,7 +8,7 @@ {{ 'addon.mod_assign.feedbacknotsupported' | translate }}

-

diff --git a/src/addons/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html b/src/addons/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html index 0359f704c..e466eb7b5 100644 --- a/src/addons/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html +++ b/src/addons/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html @@ -8,7 +8,7 @@ {{ 'addon.mod_assign.submissionnotsupported' | translate }}

-

diff --git a/src/addons/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html b/src/addons/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html index d622f39aa..3bcfbbb0f 100644 --- a/src/addons/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html +++ b/src/addons/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html @@ -3,8 +3,8 @@

{{ plugin.name }}

- +

diff --git a/src/addons/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html b/src/addons/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html index 639d40c26..fe1dc1a17 100644 --- a/src/addons/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html +++ b/src/addons/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html @@ -4,8 +4,8 @@

{{ plugin.name }}

{{ 'addon.mod_assign.numwords' | translate: {'$a': words} }}

- +

diff --git a/src/addons/mod/book/components/index/addon-mod-book-index.html b/src/addons/mod/book/components/index/addon-mod-book-index.html index 8018382c7..4727f8e6c 100644 --- a/src/addons/mod/book/components/index/addon-mod-book-index.html +++ b/src/addons/mod/book/components/index/addon-mod-book-index.html @@ -33,7 +33,7 @@ -
+
{{ 'core.start' | translate }} diff --git a/src/addons/mod/folder/components/index/addon-mod-folder-index.html b/src/addons/mod/folder/components/index/addon-mod-folder-index.html index 9b5ac6cad..d9e0cf63d 100644 --- a/src/addons/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addons/mod/folder/components/index/addon-mod-folder-index.html @@ -32,6 +32,6 @@ - diff --git a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html index c8916bc9b..67c59580d 100644 --- a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html @@ -30,7 +30,7 @@ -
+
{{ 'core.start' | translate }} diff --git a/src/addons/mod/lesson/pages/player/player.html b/src/addons/mod/lesson/pages/player/player.html index 5e49e931a..90b910843 100644 --- a/src/addons/mod/lesson/pages/player/player.html +++ b/src/addons/mod/lesson/pages/player/player.html @@ -75,12 +75,11 @@ - - - - + + + diff --git a/src/addons/mod/lesson/pages/user-retake/user-retake.html b/src/addons/mod/lesson/pages/user-retake/user-retake.html index 6b90ad1b0..428f88da9 100644 --- a/src/addons/mod/lesson/pages/user-retake/user-retake.html +++ b/src/addons/mod/lesson/pages/user-retake/user-retake.html @@ -87,7 +87,7 @@

{{ 'addon.mod_lesson.question' | translate }}

- diff --git a/src/addons/mod/page/components/index/addon-mod-page-index.html b/src/addons/mod/page/components/index/addon-mod-page-index.html index 0e92db006..b93d43391 100644 --- a/src/addons/mod/page/components/index/addon-mod-page-index.html +++ b/src/addons/mod/page/components/index/addon-mod-page-index.html @@ -32,5 +32,5 @@ - + diff --git a/src/addons/mod/resource/components/index/addon-mod-resource-index.html b/src/addons/mod/resource/components/index/addon-mod-resource-index.html index 9ce2df420..fd4d8b5b5 100644 --- a/src/addons/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addons/mod/resource/components/index/addon-mod-resource-index.html @@ -79,7 +79,7 @@ -

+
diff --git a/src/addons/mod/url/components/index/addon-mod-url-index.html b/src/addons/mod/url/components/index/addon-mod-url-index.html index 085321c3f..6b29fa18c 100644 --- a/src/addons/mod/url/components/index/addon-mod-url-index.html +++ b/src/addons/mod/url/components/index/addon-mod-url-index.html @@ -37,7 +37,7 @@ -
+
diff --git a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html index e36cc1c0f..5dd51d9ff 100644 --- a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html +++ b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html @@ -58,8 +58,8 @@

{{ 'addon.mod_workshop.conclusion' | translate }}

- +
@@ -91,8 +91,8 @@

{{ 'addon.mod_workshop.areainstructauthors' | translate }}

- +
@@ -141,7 +141,7 @@

{{ 'addon.mod_workshop.areainstructreviewers' | translate }}

- diff --git a/src/addons/notifications/pages/list/list.html b/src/addons/notifications/pages/list/list.html index 17f97fcf5..965a5158d 100644 --- a/src/addons/notifications/pages/list/list.html +++ b/src/addons/notifications/pages/list/list.html @@ -64,7 +64,7 @@ + [collapsible-item]="120"> diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts index 976e65827..e9fcb9d6b 100644 --- a/src/core/directives/collapsible-footer.ts +++ b/src/core/directives/collapsible-footer.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core'; +import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ScrollDetail } from '@ionic/core'; import { IonContent } from '@ionic/angular'; import { CoreUtils } from '@services/utils/utils'; @@ -34,8 +34,11 @@ import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@sin }) export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { + @Input() appearOnBottom = false; + protected element: HTMLElement; protected initialHeight = 0; + protected finalHeight = 0; protected initialPaddingBottom = '0px'; protected previousTop = 0; protected previousHeight = 0; @@ -58,6 +61,12 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { // Set a minimum height value. this.initialHeight = this.element.getBoundingClientRect().height || 48; + const moduleNav = this.element.querySelector('core-course-module-navigation'); + if (moduleNav) { + this.element.classList.add('has-module-nav'); + this.finalHeight = this.initialHeight - (moduleNav.getBoundingClientRect().height); + } + this.previousHeight = this.initialHeight; this.content?.style.setProperty('--core-collapsible-footer-max-height', this.initialHeight + 'px'); @@ -127,12 +136,12 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { */ protected onScroll(scrollDetail: ScrollDetail, scrollElement: HTMLElement): void { const maxScroll = scrollElement.scrollHeight - scrollElement.offsetHeight; - if (scrollDetail.scrollTop <= 0 || scrollDetail.scrollTop >= maxScroll) { + if (scrollDetail.scrollTop <= 0 || (this.appearOnBottom && scrollDetail.scrollTop >= maxScroll)) { // Reset. this.setBarHeight(this.initialHeight); } else { let newHeight = this.previousHeight - (scrollDetail.scrollTop - this.previousTop); - newHeight = CoreMath.clamp(newHeight, 0, this.initialHeight); + newHeight = CoreMath.clamp(newHeight, this.finalHeight, this.initialHeight); this.setBarHeight(newHeight); } @@ -149,12 +158,14 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { clearTimeout(this.endAnimationTimeout); } - this.element.classList.toggle('footer-collapsed', height <= 0); - this.element.classList.toggle('footer-expanded', height >= this.initialHeight); + 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 (height > 0 && height < this.initialHeight) { + if (!collapsed && !expanded) { // Finish opening or closing the bar. this.endAnimationTimeout = window.setTimeout(() => this.endAnimation(height), 500); } @@ -166,7 +177,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { * @param height Last height used. */ protected endAnimation(height: number): void { - const newHeight = height < this.initialHeight / 2 ? 0 : this.initialHeight; + const newHeight = (height - this.finalHeight) < (this.initialHeight - this.finalHeight) / 2 + ? this.finalHeight + : this.initialHeight; this.setBarHeight(newHeight); } @@ -181,6 +194,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.listenScrollEvents(); + // Only if not present or explicitly falsy it will be false. + this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); + // Recalculate the height if a parent core-loading displays the content. this.loadingChangedListener = CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data: CoreEventLoadingChangedData) => { diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts index dab6b839c..9bb83a3ca 100644 --- a/src/core/directives/collapsible-header.ts +++ b/src/core/directives/collapsible-header.ts @@ -363,8 +363,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest } const scrollableHeight = contentScroll.scrollHeight - contentScroll.clientHeight; - const collapsedHeight = expandedHeaderHeight - (expandedHeader.clientHeight ?? 0); - const frozen = scrollableHeight + collapsedHeight <= 2 * expandedHeaderHeight; + const frozen = scrollableHeight <= scrollingHeight; const progress = frozen ? 0 : CoreMath.clamp(contentScroll.scrollTop / scrollingHeight, 0, 1); @@ -377,9 +376,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest .entries(progress > .5 ? collapsedFontStyles : expandedFontStyles) .forEach(([property, value]) => floatingTitle.style.setProperty(property, value as string)); - if (progress > 0 || progress < 1) { + if (progress > 0 && progress < 1) { // Finish opening or closing the bar. - this.endAnimationTimeout = window.setTimeout(() => this.endAnimation(progress), 500); + this.endAnimationTimeout = window.setTimeout(() => this.endAnimation(progress, contentScroll.scrollTop), 500); } }); } @@ -388,8 +387,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest * End of animation when stop scrolling. * * @param progress Progress. + * @param scrollTop Current ScrollTop position. */ - protected endAnimation(progress: number): void { + protected endAnimation(progress: number, scrollTop: number): void { if(!this.page) { return; } @@ -398,6 +398,14 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest this.page.style.setProperty('--collapsible-header-progress', collapse ? '1' : '0'); this.page.classList.toggle('is-collapsed', collapse); + + if (collapse && this.scrollingHeight && this.scrollingHeight > 0 && scrollTop < this.scrollingHeight) { + this.content?.scrollToPoint(null, this.scrollingHeight); + } + + if (!collapse && this.scrollingHeight && this.scrollingHeight > 0 && scrollTop > 0) { + this.content?.scrollToPoint(null, 0); + } } } diff --git a/src/core/directives/collapsible-item.ts b/src/core/directives/collapsible-item.ts index b097611a3..32db35299 100644 --- a/src/core/directives/collapsible-item.ts +++ b/src/core/directives/collapsible-item.ts @@ -46,6 +46,7 @@ export class CoreCollapsibleItemDirective implements OnInit { protected toggleExpandEnabled = false; protected expanded = false; protected maxHeight = defaultMaxHeight; + protected expandedHeight = 0; protected loadingChangedListener?: CoreEventObserver; constructor(el: ElementRef) { @@ -72,9 +73,10 @@ export class CoreCollapsibleItemDirective implements OnInit { return; } + this.element.classList.add('collapsible-item'); + // Calculate the height now. 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. this.loadingChangedListener = @@ -82,7 +84,6 @@ export class CoreCollapsibleItemDirective implements OnInit { if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) { // The element is inside the loading, re-calculate the height. await this.calculateHeight(); - setTimeout(() => this.calculateHeight(), 200); } }); } @@ -93,9 +94,15 @@ export class CoreCollapsibleItemDirective implements OnInit { * @param element Element. */ protected async waitFormatTextsRendered(element: Element): Promise { - const formatTexts = Array - .from(element.querySelectorAll('core-format-text')) - .map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); + let formatTextElements: HTMLElement[] = []; + + 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())); } @@ -103,22 +110,25 @@ export class CoreCollapsibleItemDirective implements OnInit { /** * Calculate the height and check if we need to display show more or not. */ - protected async calculateHeight(): Promise { - await this.waitFormatTextsRendered(this.element); - + protected async calculateHeight(retries = 3): Promise { // Remove max-height (if any) to calculate the real height. - const initialMaxHeight = this.element.style.maxHeight; - this.element.style.maxHeight = 'none'; + this.element.classList.add('collapsible-loading-height'); + + await this.waitFormatTextsRendered(this.element); await CoreUtils.nextTick(); - const height = CoreDomUtils.getElementHeight(this.element) || 0; + this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0; // Restore the max height now. - this.element.style.maxHeight = initialMaxHeight; + this.element.classList.remove('collapsible-loading-height'); // 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 { if (maxHeight) { this.element.style.setProperty('--max-height', maxHeight + buttonHeight + 'px'); + } else if (this.expandedHeight) { + this.element.style.setProperty('--max-height', this.expandedHeight + 'px'); } else { this.element.style.removeProperty('--max-height'); + } } @@ -195,7 +208,7 @@ export class CoreCollapsibleItemDirective implements OnInit { * * @param e Click event. */ - protected elementClicked(e: MouseEvent): void { + elementClicked(e: MouseEvent): void { if (e.defaultPrevented) { // Ignore it if the event was prevented by some other listener. return; diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index 90030cdf2..a879c3c83 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -22,10 +22,10 @@ import { SimpleChange, Optional, ViewContainerRef, + ViewChild, } from '@angular/core'; import { IonContent } from '@ionic/angular'; -import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; 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 { CoreSubscriptions } from '@singletons/subscriptions'; 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 @@ -55,6 +56,8 @@ import { CoreComponentsRegistry } from '@singletons/components-registry'; }) export class CoreFormatTextDirective implements OnChanges { + @ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective; + @Input() text?: string; // The text to format. @Input() siteId?: string; // Site ID to use. @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() 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. - * 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; // Called when the data is rendered. @Output() onClick: EventEmitter = new EventEmitter(); // Called when clicked. protected element: HTMLElement; - protected expanded = false; - protected loadingChangedListener?: CoreEventObserver; protected emptyText = ''; protected contentSpan: HTMLElement; - protected toggleExpandEnabled = false; constructor( element: ElementRef, @@ -115,6 +113,8 @@ export class CoreFormatTextDirective implements OnChanges { this.afterRender = new EventEmitter(); this.element.addEventListener('click', this.elementClicked.bind(this)); + + this.siteId = this.siteId || CoreSites.getCurrentSiteId(); } /** @@ -122,8 +122,6 @@ export class CoreFormatTextDirective implements OnChanges { */ ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) { - this.setExpandButtonEnabled(false); - this.formatAndRenderContents(); } } @@ -148,9 +146,13 @@ export class CoreFormatTextDirective implements OnChanges { * Apply CoreExternalContentDirective to a certain element. * * @param element Element to add the attributes to. - * @return External content instance. + * @return External content instance or undefined if siteId is not provided. */ - protected addExternalContent(element: Element): CoreExternalContentDirective { + protected addExternalContent(element: Element): CoreExternalContentDirective | undefined { + if (!this.siteId) { + return; + } + // Angular doesn't let adding directives dynamically. Create the CoreExternalContentDirective manually. const extContent = new CoreExternalContentDirective(new ElementRef(element)); @@ -269,101 +271,6 @@ export class CoreFormatTextDirective implements OnChanges { }); } - /** - * Calculate the height and check if we need to display show more or not. - */ - protected async calculateHeight(): Promise { - // @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. * @@ -385,24 +292,18 @@ export class CoreFormatTextDirective implements OnChanges { return; } - if (!this.toggleExpandEnabled) { - // Nothing to do on click, just stop. - return; - } - - e.preventDefault(); - e.stopPropagation(); - - this.toggleExpand(); + this.collapsible?.elementClicked(e); } /** * Finish the rendering, displaying the element again and calling afterRender. */ - protected finishRender(): void { + protected async finishRender(): Promise { // Show the element again. this.element.classList.remove('core-format-text-loading'); + await CoreUtils.nextTick(); + // Emit the afterRender output. this.afterRender.emit(); } @@ -413,15 +314,12 @@ export class CoreFormatTextDirective implements OnChanges { protected async formatAndRenderContents(): Promise { if (!this.text) { this.contentSpan.innerHTML = this.emptyText; // Remove current contents. - this.finishRender(); + + await this.finishRender(); 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')) { this.element.setAttribute('singleLine', String(CoreUtils.isTrueOrOne(this.singleLine))); } @@ -434,36 +332,22 @@ export class CoreFormatTextDirective implements OnChanges { this.element.classList.add('core-disable-media-adapt'); 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. - CoreDomUtils.moveChildren(result.div, this.contentSpan); + // Move the children to the current element to be able to calculate the height. + CoreDomUtils.moveChildren(result.div, this.contentSpan); - // Calculate the height now. - this.calculateHeight(); - setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong. + await CoreUtils.nextTick(); - // Add magnifying glasses to images. - this.addMagnifyingGlasses(); - - if (!this.loadingChangedListener) { - // Recalculate the height if a parent core-loading displays the content. - 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(); + // Use collapsible-item directive instead. + if (this.maxHeight && !this.collapsible) { + this.collapsible = new CoreCollapsibleItemDirective(new ElementRef(this.element)); + this.collapsible.height = this.maxHeight; + this.collapsible.ngOnInit(); } + // Add magnifying glasses to images. + this.addMagnifyingGlasses(); + 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. CoreFilterDelegate.handleHtml( @@ -479,7 +363,7 @@ export class CoreFormatTextDirective implements OnChanges { } this.element.classList.remove('core-disable-media-adapt'); - this.finishRender(); + await this.finishRender(); } /** @@ -581,7 +465,7 @@ export class CoreFormatTextDirective implements OnChanges { this.addMediaAdaptClass(img); const externalImage = this.addExternalContent(img); - if (!externalImage.invalid) { + if (externalImage && !externalImage.invalid) { externalImages.push(externalImage); } diff --git a/src/core/features/course/components/module-description/core-course-module-description.html b/src/core/features/course/components/module-description/core-course-module-description.html index 0f6d34c0a..1476d7476 100644 --- a/src/core/features/course/components/module-description/core-course-module-description.html +++ b/src/core/features/course/components/module-description/core-course-module-description.html @@ -1,7 +1,7 @@ - diff --git a/src/core/features/course/components/module-info/core-course-module-info.html b/src/core/features/course/components/module-info/core-course-module-info.html index 6078a0650..aea829834 100644 --- a/src/core/features/course/components/module-info/core-course-module-info.html +++ b/src/core/features/course/components/module-info/core-course-module-info.html @@ -13,14 +13,6 @@
- - - - - - -
+ + + + + + + + diff --git a/src/core/features/course/components/module-navigation/core-course-module-navigation.html b/src/core/features/course/components/module-navigation/core-course-module-navigation.html index 6b092bfb8..5ca66de46 100644 --- a/src/core/features/course/components/module-navigation/core-course-module-navigation.html +++ b/src/core/features/course/components/module-navigation/core-course-module-navigation.html @@ -3,13 +3,15 @@ - + + {{ 'core.previous' | translate }} - + {{ 'core.next' | translate }} + diff --git a/src/core/features/course/components/module-summary/module-summary.html b/src/core/features/course/components/module-summary/module-summary.html index f60653532..a510cf893 100644 --- a/src/core/features/course/components/module-summary/module-summary.html +++ b/src/core/features/course/components/module-summary/module-summary.html @@ -49,7 +49,7 @@ {{ 'core.description' | translate}}

+ [contextInstanceId]="module.id" [courseId]="courseId" [collapsible-item]="120"> @@ -169,7 +169,7 @@

{{ 'core.grades.feedback' | translate}}

-

diff --git a/src/core/features/course/pages/course-summary/course-summary.html b/src/core/features/course/pages/course-summary/course-summary.html index 88300031a..9fbfb57a4 100644 --- a/src/core/features/course/pages/course-summary/course-summary.html +++ b/src/core/features/course/pages/course-summary/course-summary.html @@ -72,7 +72,8 @@

{{'core.summary' | translate}}

- +
@@ -104,7 +105,7 @@ : - diff --git a/src/core/features/course/pages/module-preview/module-preview.html b/src/core/features/course/pages/module-preview/module-preview.html index 11cfb10da..2dfdb164f 100644 --- a/src/core/features/course/pages/module-preview/module-preview.html +++ b/src/core/features/course/pages/module-preview/module-preview.html @@ -45,6 +45,6 @@ - + diff --git a/src/core/features/courses/pages/categories/categories.html b/src/core/features/courses/pages/categories/categories.html index e7c8280b4..fa208cdca 100644 --- a/src/core/features/courses/pages/categories/categories.html +++ b/src/core/features/courses/pages/categories/categories.html @@ -34,7 +34,7 @@

-

diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index 6cbbae4e4..2c237cf64 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -55,7 +55,7 @@ - @@ -124,7 +124,7 @@

{{ 'core.grades.feedback' | translate}}

-

diff --git a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html index 287220e84..aeb66ed8c 100644 --- a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html +++ b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html @@ -14,5 +14,6 @@ (onLoadingContent)="contentLoading()"> - + diff --git a/src/core/features/siteplugins/components/module-index/module-index.ts b/src/core/features/siteplugins/components/module-index/module-index.ts index 3e15a13dc..bbaebb0d2 100644 --- a/src/core/features/siteplugins/components/module-index/module-index.ts +++ b/src/core/features/siteplugins/components/module-index/module-index.ts @@ -85,6 +85,8 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C */ size?: string; + collapsibleFooterAppearOnBottom = true; + displayOpenInBrowser = true; displayDescription = true; displayRefresh = true; @@ -133,6 +135,8 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C this.displaySize = !CoreUtils.isFalseOrZero(handlerSchema.displaysize); this.displayGrades = CoreUtils.isTrueOrOne(handlerSchema.displaygrades); // False by default. this.ptrEnabled = !CoreUtils.isFalseOrZero(handlerSchema.ptrenabled); + + this.collapsibleFooterAppearOnBottom = !CoreUtils.isFalseOrZero(handlerSchema.isresource); } // Get the data for the context menu. diff --git a/src/theme/components/collapsible-item.scss b/src/theme/components/collapsible-item.scss new file mode 100644 index 000000000..979f5854a --- /dev/null +++ b/src/theme/components/collapsible-item.scss @@ -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; + } + } + } + } +} diff --git a/src/theme/components/format-text.scss b/src/theme/components/format-text.scss index 02581a519..7cca7ef2c 100644 --- a/src/theme/components/format-text.scss +++ b/src/theme/components/format-text.scss @@ -48,14 +48,6 @@ core-format-text { opacity: 0; display: inline; } - - .collapsible-toggle { - display: none !important; - } - } - - .collapsible-toggle { - display: none; } .core-format-text-content { @@ -68,33 +60,20 @@ core-format-text { word-wrap: break-word; } - &[maxHeight], - &[ng-reflect-max-height] { + &.collapsible-item { 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. - &.collapsible-enabled { - cursor: pointer; - pointer-events: auto; + cursor: pointer; + pointer-events: auto; - @include collapsible-item(); + .core-format-text-content { + display: block; } - } - @if ($core-format-text-never-shorten) { - &[maxHeight], - &[ng-reflect-max-height] { - &.collapsible-enabled.collapsible-expanded { - max-height: none !important; + @if ($core-format-text-never-shorten) { + &.collapsible-enabled { + --display-toggle: none !important; + --max-height: none !important; .collapsible-toggle { display: none !important; diff --git a/src/theme/globals.mixins.scss b/src/theme/globals.mixins.scss index cbde34e92..9c0882ebb 100644 --- a/src/theme/globals.mixins.scss +++ b/src/theme/globals.mixins.scss @@ -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. @function get_brightness($color) { @return (red($color) + green($color) + blue($color)) / 3; diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index e39774de1..a08dc3fa9 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1419,15 +1419,21 @@ ion-grid.core-no-grid > ion-row { right: 0; } -[collapsible-item] { - @include collapsible-item(); -} - [collapsible-footer] { &.footer-collapsed { --core-collapsible-footer-height: 0; opacity: 0; } + &.has-module-nav.footer-collapsed { + --core-collapsible-footer-height: auto; + opacity: 1; + core-course-module-navigation { + height: 0; + opacity: 0; + @include core-transition(all, 200ms); + } + + } &.footer-expanded { --core-collapsible-footer-height: auto; } diff --git a/src/theme/theme.scss b/src/theme/theme.scss index cd0769b6d..83f8fc8d9 100644 --- a/src/theme/theme.scss +++ b/src/theme/theme.scss @@ -20,6 +20,7 @@ /* Components */ @import "./components/collapsible-header.scss"; +@import "./components/collapsible-item.scss"; @import "./components/format-text.scss"; @import "./components/rubrics.scss"; @import "./components/mod-label.scss";